mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-08 12:35:01 +00:00
Cut per-frame allocations in rendering mixins (#2182)
This commit is contained in:
1 parent
fe67a22d5a
commit
6c888a634a
139 files changed
+7435
-1376
No files matched your search
@@ -178,6 +178,7 @@ val glCoreTest by tasks.registering(Test::class) {
|
||||
tasks.test { finalizedBy(glCoreTest) }
|
||||
|
||||
apply(from = "gradle/angelica-shadow-common.gradle.kts")
|
||||
apply(from = "experimental-surround/surround.root.gradle.kts")
|
||||
|
||||
tasks.withType<TaskPublishCurseForge>().configureEach {
|
||||
uploadArtifacts.forEach { it.addGameVersion("Client") }
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
apply(from = rootProject.file("experimental-surround/surround.project.gradle.kts"))
|
||||
|
||||
val mixinProviderSpec = rootProject.extra["mixinProviderSpec"] as String
|
||||
|
||||
val refmapProbe = sourceSets.create("refmapProbe")
|
||||
|
||||
dependencies {
|
||||
compileOnly(mixinProviderSpec)
|
||||
testImplementation(mixinProviderSpec)
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(project(":experimental-surround-runtime"))
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
"refmapProbeCompileOnly"(mixinProviderSpec)
|
||||
"refmapProbeCompileOnly"(project(":experimental-surround-runtime"))
|
||||
"refmapProbeAnnotationProcessor"(mixinProviderSpec)
|
||||
"refmapProbeAnnotationProcessor"(sourceSets.main.get().output)
|
||||
}
|
||||
|
||||
val probeRefmaps = layout.buildDirectory.dir("refmap-probe")
|
||||
val probeSrg = file("src/refmapProbe/mappings.srg")
|
||||
val surroundAp = "com.gtnewhorizons.angelica.experimental.surround.ap.SurroundProcessor"
|
||||
val mixinAps = "org.spongepowered.tools.obfuscation.MixinObfuscationProcessorInjection,org.spongepowered.tools.obfuscation.MixinObfuscationProcessorTargets"
|
||||
|
||||
fun JavaCompile.probe(name: String, processors: String?) {
|
||||
val refmap = probeRefmaps.map { it.file("refmap-probe/$name.json") }
|
||||
val srg = probeSrg
|
||||
javaCompiler = javaToolchains.compilerFor { languageVersion = JavaLanguageVersion.of(25) }
|
||||
outputs.file(refmap)
|
||||
inputs.file(srg)
|
||||
options.compilerArgumentProviders.add(CommandLineArgumentProvider {
|
||||
listOf("-proc:only", "-AoutRefMapFile=" + refmap.get().asFile.absolutePath,
|
||||
"-AreobfSrgFile=" + srg.absolutePath, "-AdefaultObfuscationEnv=searge") +
|
||||
(if (processors == null) emptyList() else listOf("-processor", processors))
|
||||
})
|
||||
}
|
||||
|
||||
fun JavaCompile.probeOrdered(name: String, classesDir: String, processors: String) {
|
||||
source = refmapProbe.java
|
||||
classpath = refmapProbe.compileClasspath
|
||||
options.annotationProcessorPath = refmapProbe.annotationProcessorPath
|
||||
destinationDirectory = layout.buildDirectory.dir("classes/$classesDir")
|
||||
probe(name, processors)
|
||||
}
|
||||
|
||||
tasks.named<JavaCompile>(refmapProbe.compileJavaTaskName) { probe("discovered", null) }
|
||||
val probeSurroundFirst by tasks.registering(JavaCompile::class) { probeOrdered("surround-first", "refmapProbeFirst", "$surroundAp,$mixinAps") }
|
||||
val probeSurroundLast by tasks.registering(JavaCompile::class) { probeOrdered("surround-last", "refmapProbeLast", "$mixinAps,$surroundAp") }
|
||||
|
||||
sourceSets.test { resources.srcDir(files(probeRefmaps).builtBy(refmapProbe.compileJavaTaskName, probeSurroundFirst, probeSurroundLast)) }
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.ap;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.tools.obfuscation.mirror.AnnotationHandle;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@SupportedAnnotationTypes("com.gtnewhorizons.angelica.experimental.surround.Surround")
|
||||
public class SurroundProcessor extends AbstractProcessor {
|
||||
|
||||
record Registration(TypeElement mixin, ExecutableElement method, AnnotationMirror surround) {
|
||||
}
|
||||
|
||||
private final List<Registration> registrations = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
return SourceVersion.latestSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
if (roundEnv.processingOver()) {
|
||||
if (!this.registrations.isEmpty()) {
|
||||
register();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (TypeElement annotation : annotations) {
|
||||
for (Element annotated : roundEnv.getElementsAnnotatedWith(annotation)) {
|
||||
if (annotated.getEnclosingElement() instanceof TypeElement mixin && AnnotationHandle.of(mixin, Mixin.class).exists()) {
|
||||
for (AnnotationMirror mirror : annotated.getAnnotationMirrors()) {
|
||||
if (mirror.getAnnotationType().asElement().equals(annotation)) {
|
||||
this.registrations.add(new Registration(mixin, (ExecutableElement) annotated, mirror));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void register() {
|
||||
final ProcessingEnvironment env = this.processingEnv;
|
||||
try {
|
||||
// pinned by UniMixins 0.3.1; recheck on bump
|
||||
final Class<?> annotatedMixins = Class.forName("org.spongepowered.tools.obfuscation.AnnotatedMixins", false, AnnotationHandle.class.getClassLoader());
|
||||
final Method forEnvironment = annotatedMixins.getDeclaredMethod("getMixinsForEnvironment", ProcessingEnvironment.class);
|
||||
final Method registerInjector = annotatedMixins.getDeclaredMethod("registerInjector", TypeElement.class, ExecutableElement.class, AnnotationHandle.class);
|
||||
final Method writeReferences = annotatedMixins.getDeclaredMethod("writeReferences");
|
||||
forEnvironment.setAccessible(true);
|
||||
registerInjector.setAccessible(true);
|
||||
writeReferences.setAccessible(true);
|
||||
final Object mixins = forEnvironment.invoke(null, env);
|
||||
for (Registration registration : this.registrations) {
|
||||
registerInjector.invoke(mixins, registration.mixin(), registration.method(), AnnotationHandle.of(registration.surround()));
|
||||
}
|
||||
writeReferences.invoke(mixins);
|
||||
} catch (ReflectiveOperationException | LinkageError | RuntimeException e) {
|
||||
env.getMessager().printMessage(Diagnostic.Kind.ERROR, "@Surround: refmap entries were not written: " + e + (e.getCause() == null ? "" : " caused by " + e.getCause()));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
com.gtnewhorizons.angelica.experimental.surround.ap.SurroundProcessor
|
||||
@@ -0,0 +1,20 @@
|
||||
package probe;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(RefmapTarget.class)
|
||||
class MixinRefmapTarget {
|
||||
@Surround(method = "run()V")
|
||||
private void wholeMethod() {}
|
||||
|
||||
@Surround.Finally
|
||||
private void always() {}
|
||||
|
||||
@Surround(method = "run(Ljava/lang/String;)V", id = "call", at = @At(value = "INVOKE", target = "Lprobe/RefmapTarget;helper()V"))
|
||||
private void callForm() {}
|
||||
|
||||
@Surround.Finally("call")
|
||||
private void alwaysCall() {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package probe;
|
||||
|
||||
public class RefmapTarget {
|
||||
void run() {}
|
||||
void run(String s) { helper(); }
|
||||
void helper() {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
MD: probe/RefmapTarget/run ()V probe/RefmapTarget/func_1_a ()V
|
||||
MD: probe/RefmapTarget/run (Ljava/lang/String;)V probe/RefmapTarget/func_2_b (Ljava/lang/String;)V
|
||||
MD: probe/RefmapTarget/helper ()V probe/RefmapTarget/func_3_c ()V
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.ap;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class RefmapRegistrationTest {
|
||||
|
||||
private static final Pattern ENTRY = Pattern.compile("\"([^\"]+)\": \"([^\"]+)\"");
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"discovered", "surround-first", "surround-last"})
|
||||
void surroundTargetsAreRemappedInTheRefmap(String variant) throws IOException {
|
||||
final Map<String, String> expected = new HashMap<>();
|
||||
expected.put("run()V", "Lprobe/RefmapTarget;func_1_a()V");
|
||||
expected.put("run(Ljava/lang/String;)V", "Lprobe/RefmapTarget;func_2_b(Ljava/lang/String;)V");
|
||||
expected.put("Lprobe/RefmapTarget;helper()V", "Lprobe/RefmapTarget;func_3_c()V");
|
||||
assertEquals(expected, mappings(variant, "probe/MixinRefmapTarget"));
|
||||
}
|
||||
|
||||
private static Map<String, String> mappings(String variant, String mixin) throws IOException {
|
||||
final String json = new String(RefmapRegistrationTest.class.getResourceAsStream("/refmap-probe/" + variant + ".json").readAllBytes(), UTF_8);
|
||||
final int start = json.indexOf('{', json.indexOf("\"" + mixin + "\"", json.indexOf("\"mappings\"")));
|
||||
final Matcher m = ENTRY.matcher(json.substring(start, json.indexOf('}', start)));
|
||||
final Map<String, String> entries = new HashMap<>();
|
||||
while (m.find()) entries.put(m.group(1), m.group(2));
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
apply(plugin = "xyz.wagyourtail.jvmdowngrader")
|
||||
|
||||
apply(from = rootProject.file("experimental-surround/surround.project.gradle.kts"))
|
||||
|
||||
// Required by RFG consumers (runClient17 java17Dependencies) to pick a variant.
|
||||
val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation", String::class.java)
|
||||
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
|
||||
|
||||
configurations {
|
||||
listOf("apiElements", "runtimeElements").forEach { name ->
|
||||
named(name) {
|
||||
attributes {
|
||||
attribute(rfgObfAttr, "mcp")
|
||||
attribute(rfgTransformedAttr, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mixinProviderSpec = rootProject.extra["mixinProviderSpec"] as String
|
||||
|
||||
dependencies {
|
||||
compileOnly(mixinProviderSpec)
|
||||
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(mixinProviderSpec)
|
||||
testImplementation(libs.launchwrapper)
|
||||
testImplementation(libs.commons.io)
|
||||
testImplementation(libs.guava)
|
||||
testImplementation(libs.log4j.core)
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testRuntimeOnly(libs.jvmdowngrader.java.api) { artifact { classifier = "downgraded-8" } }
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "WagYourTail"
|
||||
url = uri("https://maven.wagyourtail.xyz/releases")
|
||||
content { includeGroup("xyz.wagyourtail.jvmdowngrader") }
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeFiles>().configureEach {
|
||||
downgradeTo.set(JavaVersion.VERSION_1_8)
|
||||
multiReleaseOriginal.set(false)
|
||||
multiReleaseVersions.set(emptySet())
|
||||
logLevel.set("FATAL")
|
||||
}
|
||||
|
||||
fun SourceSet.registerDowngrade(taskName: String) = tasks.register<DowngradeFiles>(taskName) {
|
||||
inputCollection = output.classesDirs
|
||||
classpath = compileClasspath
|
||||
output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
|
||||
dependsOn(tasks.named(classesTaskName))
|
||||
}
|
||||
|
||||
val downgradeMainClasses = sourceSets["main"].registerDowngrade("downgradeMainClasses")
|
||||
val downgradeTestClasses = sourceSets["test"].registerDowngrade("downgradeTestClasses")
|
||||
|
||||
val goldenDir = layout.projectDirectory.dir("src/test/resources/golden").asFile.absolutePath
|
||||
|
||||
// Runs on Java 8 against the JVMDowngrader output, the bytecode production ships.
|
||||
fun Test.configureSurroundTest(runDirName: String) {
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(8)
|
||||
if (System.getProperty("os.name").lowercase().contains("mac") && System.getProperty("os.arch") == "aarch64") {
|
||||
vendor = JvmVendorSpec.AZUL
|
||||
}
|
||||
}
|
||||
dependsOn(downgradeMainClasses, downgradeTestClasses)
|
||||
val downgradedTest = files(downgradeTestClasses.map { it.outputCollection })
|
||||
testClassesDirs = downgradedTest
|
||||
classpath = downgradedTest
|
||||
.plus(files(downgradeMainClasses.map { it.outputCollection }))
|
||||
.plus(sourceSets["test"].runtimeClasspath.minus(sourceSets["main"].output.classesDirs).minus(sourceSets["test"].output.classesDirs))
|
||||
val dir = layout.buildDirectory.dir(runDirName).get().asFile
|
||||
workingDir = dir
|
||||
systemProperty("surround.golden.dir", goldenDir)
|
||||
systemProperty("mixin.debug.verify", "true")
|
||||
val javac = javaToolchains.compilerFor { languageVersion = JavaLanguageVersion.of(25) }
|
||||
jvmArgumentProviders.add(CommandLineArgumentProvider { listOf("-Dsurround.javac=" + javac.get().executablePath.asFile.absolutePath) })
|
||||
doFirst { dir.mkdirs() }
|
||||
}
|
||||
|
||||
fun Test.configureUntaggedSurroundTest(runDirName: String, vararg systemProps: Pair<String, String>) {
|
||||
useJUnitPlatform { excludeTags("mixinextras") }
|
||||
for ((key, value) in systemProps) {
|
||||
systemProperty(key, value)
|
||||
}
|
||||
configureSurroundTest(runDirName)
|
||||
}
|
||||
|
||||
fun Test.configureTaggedSurroundTest(runDirName: String, tag: String, vararg systemProps: Pair<String, String>) {
|
||||
useJUnitPlatform { includeTags(tag) }
|
||||
for ((key, value) in systemProps) {
|
||||
systemProperty(key, value)
|
||||
}
|
||||
configureSurroundTest(runDirName)
|
||||
}
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
configureUntaggedSurroundTest("test-run")
|
||||
}
|
||||
|
||||
val mixinExtrasTest by tasks.registering(Test::class) {
|
||||
configureTaggedSurroundTest(
|
||||
"test-run-mixinextras",
|
||||
"mixinextras",
|
||||
"surround.test.mixinextras" to "true"
|
||||
)
|
||||
}
|
||||
|
||||
val mixinExtrasAdditionsFirstTest by tasks.registering(Test::class) {
|
||||
configureTaggedSurroundTest(
|
||||
"test-run-mixinextras-additions-first",
|
||||
"mixinextras",
|
||||
"surround.test.mixinextras" to "true",
|
||||
"surround.test.additionsFirst" to "true"
|
||||
)
|
||||
}
|
||||
|
||||
val obfTest by tasks.registering(Test::class) {
|
||||
configureUntaggedSurroundTest("test-run-obf", "surround.test.obf" to "true")
|
||||
filter { excludeTestsMatching("*.RejectionTest") }
|
||||
}
|
||||
|
||||
tasks.named("check") {
|
||||
dependsOn(mixinExtrasTest, mixinExtrasAdditionsFirstTest, obfTest)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Exits;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode;
|
||||
|
||||
record CallSiteSpec(MethodNode target, InjectionNode node, MethodNode entry, String entryDesc, Type[] operands, Type result, Exits exits, String description, int[] entryLocals, int[][] locals) {}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Slice;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Experimental and internal to Angelica; the API may change without notice.
|
||||
* <p>
|
||||
* Surrounds one method call ({@link #at()} set) or a whole method ({@link #at()} empty) with try/catch/finally.
|
||||
* The call form replaces MixinExtras' {@code @WrapOperation} and the whole-method form replaces {@code @WrapMethod},
|
||||
* without their per-call {@code Operation} and {@code Object[]} allocation. Use it when the original runs at most once
|
||||
* with its original arguments.
|
||||
* <p>
|
||||
* A surround can:
|
||||
* <ul>
|
||||
* <li>run code before the original: the entry handler, the annotated method itself;</li>
|
||||
* <li>hand state from that code to everything after it: {@link Carry @Surround.Carry} locals;</li>
|
||||
* <li>read the call's operands, the target's parameters, and locals at the call: operands,
|
||||
* {@link Local @Surround.Local};</li>
|
||||
* <li>skip the original and supply its result instead: {@link Skip @Surround.Skip} + {@link Skipped @Surround.Skipped};</li>
|
||||
* <li>replace the result after a normal completion: {@link Return @Surround.Return};</li>
|
||||
* <li>react to an exception, rethrowing or (calls only) swallowing it and supplying the result:
|
||||
* {@link Catch @Surround.Catch};</li>
|
||||
* <li>run code on every way out: {@link Finally @Surround.Finally}.</li>
|
||||
* </ul>
|
||||
* It cannot call the original twice or with different arguments.
|
||||
*
|
||||
* <h2>Example</h2>
|
||||
* <pre>
|
||||
* @Surround(method = "doRender(Lnet/minecraft/entity/item/EntityItem;DDDFF)V")
|
||||
* private void enter(EntityItem entity, double x, double y, double z, float yaw, float partialTicks) {
|
||||
* @Surround.Carry int prevItemId = ItemIdManager.getItemId();
|
||||
* ItemIdManager.setItemId(entity.getEntityItem());
|
||||
* }
|
||||
*
|
||||
* @Surround.Finally
|
||||
* private void restore(@Surround.Carry int prevItemId) {
|
||||
* ItemIdManager.setItemIdRaw(prevItemId);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <h2>Handlers</h2>
|
||||
* The annotated method is the entry handler. Exit handlers are methods in the same mixin whose {@code value} is this
|
||||
* surround's {@link #id()}:
|
||||
* <pre>
|
||||
* @Surround void enter(operands, @Local params) before the original
|
||||
* @Surround.Skipped T skipped(operands, @Local params, @Carry params) instead of the original
|
||||
* @Surround.Return T ret(T result, operands, @Local params, @Carry params) after it completes normally
|
||||
* @Surround.Catch void caught(E error, operands, @Local params, @Carry params) when it throws E
|
||||
* @Surround.Finally void always(operands, @Local params, @Carry params) on every way out
|
||||
* </pre>
|
||||
* {@code T} is the return type of the call or method. When it is {@code void}, {@code ret} takes no result and
|
||||
* {@code Skipped} returns {@code void}.
|
||||
* <ul>
|
||||
* <li>The entry handler returns {@code void}. Exit handlers are static exactly when the entry handler is.</li>
|
||||
* <li>Per surround: at most one {@code Skipped}, one {@code Return} and one {@code Finally}, and one {@code Catch} per
|
||||
* exception type. A method has one role.</li>
|
||||
* <li>{@link #id()} is required when the mixin declares more than one {@code @Surround}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Parameters</h2>
|
||||
* Declare them in this order, each group optional:
|
||||
* <ol>
|
||||
* <li>Operands, as a leading prefix. For a call: the receiver (unless static), then the arguments. A reference
|
||||
* operand may be declared as a supertype. For a whole method: the target's parameters.</li>
|
||||
* <li>{@link Local @Surround.Local} parameters (call form only). Exit handlers see the value as of entry.</li>
|
||||
* <li>{@link Carry @Surround.Carry} parameters (exit handlers only).</li>
|
||||
* </ol>
|
||||
*
|
||||
* <h2>Carrying state</h2>
|
||||
* Exit handlers see entry-handler locals only through {@link Carry @Surround.Carry}; see there for the rules. Two
|
||||
* carries of one kind are told apart by name:
|
||||
* <pre>
|
||||
* @Surround(method = ..., at = @At(value = "INVOKE", target = ...))
|
||||
* private void enter() {
|
||||
* @Surround.Carry("depth") int depth = GLStateManager.pushState(StateSet.BLEND);
|
||||
* @Surround.Carry("prev") int prevProgram = GLStateManager.getActiveProgram();
|
||||
* @Surround.Carry Class<?> prevRenderable = TesrAttribution.currentRenderable;
|
||||
* }
|
||||
*
|
||||
* @Surround.Finally
|
||||
* private void exit(@Surround.Carry("depth") int depth, @Surround.Carry("prev") int prevProgram,
|
||||
* @Surround.Carry Class<?> prevRenderable) { ... }
|
||||
* </pre>
|
||||
*
|
||||
* <h2>Entry-handler body</h2>
|
||||
* <ul>
|
||||
* <li>Every path must fall through to the end: no early {@code return}. Branch with {@code if} instead.</li>
|
||||
* <li>Do not assign its parameters. To change arguments, use {@code @ModifyVariable} or {@code @WrapMethod}.</li>
|
||||
* <li>In the call form it is inlined at every matched call, as are the exit-handler calls. Keep it short: move bulky
|
||||
* logic into a helper and carry the helper's result. Huge targets stop being JIT-compiled past 8000 bytes.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Skipping</h2>
|
||||
* A {@link Skip @Surround.Skip} local in the entry handler decides whether the original runs; see there for the rules.
|
||||
*
|
||||
* <h2>Order and exceptions</h2>
|
||||
* <ol>
|
||||
* <li>The entry handler. If it throws, nothing else runs.</li>
|
||||
* <li>The original, or {@link Skipped} when skipped.</li>
|
||||
* <li>{@link Return}, only if the original completed normally (not after {@code Skipped}). Its return value replaces
|
||||
* the result.</li>
|
||||
* <li>{@link Finally}, on every way out: normal, after a catch, or rethrowing.</li>
|
||||
* </ol>
|
||||
* {@link Catch} covers step 2, not {@code Return}, and only the most specific matching one runs. It rethrows when
|
||||
* it returns, unless {@link Catch#handle()} is set (call form only): the exception then ends there, and the
|
||||
* handler's return value becomes the call's result.
|
||||
*
|
||||
* <h2>Whole-method form</h2>
|
||||
* <ul>
|
||||
* <li>The original body becomes a private {@code name$surround} method, so stack traces gain that frame.</li>
|
||||
* <li>A {@code synchronized} target holds its lock across the handlers too.</li>
|
||||
* <li>An entry handler that calls the target goes through the surround again.</li>
|
||||
* <li>{@code @Local}, {@link #slice()} and {@code handle} are call-form only.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>With other injectors</h2>
|
||||
* <ul>
|
||||
* <li>A call surround encloses a {@code @Redirect} or {@code @WrapOperation} on the same call. A whole-method surround
|
||||
* encloses every other injection in the method, including call surrounds.</li>
|
||||
* <li>Several surrounds on one call or method nest, lowest priority innermost. An outer skip skips the inner ones
|
||||
* entirely, and {@code Return} replacements chain from inner to outer.</li>
|
||||
* <li>A matched call that another injector removed is skipped with a warning.</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Surround {
|
||||
|
||||
String[] method() default {};
|
||||
|
||||
/**
|
||||
* Calls to surround, as INVOKE-style {@code @At} without shift or by; each match is surrounded separately. Empty
|
||||
* surrounds the whole method.
|
||||
*/
|
||||
At[] at() default {};
|
||||
|
||||
/**
|
||||
* As for {@code @Inject}; calls only.
|
||||
*/
|
||||
Slice[] slice() default {};
|
||||
|
||||
/**
|
||||
* Names this surround for its exit handlers. Required when the mixin declares more than one {@code @Surround};
|
||||
* unique per mixin.
|
||||
*/
|
||||
String id() default "";
|
||||
|
||||
boolean remap() default true;
|
||||
|
||||
/**
|
||||
* As for {@code @Inject}, as are {@link #expect()} and {@link #allow()}; each surrounded call or whole target counts once.
|
||||
*/
|
||||
int require() default -1;
|
||||
|
||||
int expect() default 1;
|
||||
|
||||
int allow() default -1;
|
||||
|
||||
/**
|
||||
* Carries a value from the entry handler to the exit handlers.
|
||||
* <p>
|
||||
* Annotate a local in the entry handler, then declare a parameter with the same annotation in any exit handler that
|
||||
* needs the value:
|
||||
* <pre>
|
||||
* @Surround(method = ...)
|
||||
* private void enter() {
|
||||
* @Surround.Carry int prevItemId = ItemIdManager.getItemId();
|
||||
* ItemIdManager.setItemId(...);
|
||||
* }
|
||||
*
|
||||
* @Surround.Finally
|
||||
* private void restore(@Surround.Carry int prevItemId) {
|
||||
* ItemIdManager.setItemIdRaw(prevItemId);
|
||||
* }
|
||||
* </pre>
|
||||
* Declaring the local:
|
||||
* <ul>
|
||||
* <li>Annotate the declaration itself: {@code @Surround.Carry int x = ...}. Not a parameter of the entry handler
|
||||
* (use operands for those), and not part of the type (e.g. an array component).</li>
|
||||
* <li>Declare it at the top level of the entry handler and initialize it there. It must still be in scope and
|
||||
* definitely assigned when the entry handler ends.</li>
|
||||
* </ul>
|
||||
* Receiving it:
|
||||
* <ul>
|
||||
* <li>Carry parameters come last, after operands and {@code @Local} parameters. An exit handler may take any
|
||||
* subset.</li>
|
||||
* <li>Parameters bind to locals by kind: reference, {@code int}-like ({@code boolean}, {@code byte},
|
||||
* {@code char}, {@code short}, {@code int}), {@code long}, {@code float}, {@code double}. If the entry handler
|
||||
* carries two locals of one kind, name every one of that kind with the same {@link #value()} on the local and on
|
||||
* each parameter that reads it: {@code @Surround.Carry("depth") int depth}. Names are unique within a handler.</li>
|
||||
* <li>A reference is cast to the parameter's type on read. Every exit handler must read a given carry as the same
|
||||
* type.</li>
|
||||
* </ul>
|
||||
* Each carry lives in a local slot of the target method, with no allocation, so nested and recursive invocations
|
||||
* keep separate values. The entry handler returns {@code void}; carry its results instead.
|
||||
*/
|
||||
@Target({ElementType.TYPE_USE, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Carry {
|
||||
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether the original runs.
|
||||
* <p>
|
||||
* Annotate one {@code boolean} or {@code int}-like local in the entry handler, declared like a {@link Carry}: at
|
||||
* the top level, initialized at its declaration, in scope at the end:
|
||||
* <pre>
|
||||
* @Surround(method = ..., at = @At(value = "INVOKE", target = ...))
|
||||
* private void enter(RenderBlocks renderer, Block block) {
|
||||
* @Surround.Skip boolean skip = FallingBlockRendering.isActive();
|
||||
* }
|
||||
*
|
||||
* @Surround.Skipped
|
||||
* private boolean replacement(RenderBlocks renderer, Block block) { ... }
|
||||
* </pre>
|
||||
* <ul>
|
||||
* <li>If the local is nonzero when the entry handler ends, the original does not run and {@link Skipped} runs in
|
||||
* its place; {@link Return} does not run after it, {@link Catch} and {@link Finally} still do.</li>
|
||||
* <li>{@link Skipped} is required unless the result is {@code void}, and rejected without a {@code Skip}
|
||||
* local.</li>
|
||||
* <li>At most one per entry handler.</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Target(ElementType.TYPE_USE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Skip {
|
||||
}
|
||||
|
||||
/**
|
||||
* Supplies the result in place of a skipped original. Required unless that result is {@code void}.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Skipped {
|
||||
|
||||
/**
|
||||
* The {@link Surround#id()} of the surround this handler belongs to; empty for the default.
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs after the original completes normally. Takes the result first (none for {@code void}) and returns its
|
||||
* replacement.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Return {
|
||||
|
||||
/**
|
||||
* The {@link Surround#id()} of the surround this handler belongs to; empty for the default.
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the original or {@link Skipped} throws the first parameter's type, unless a more specific
|
||||
* {@code Catch} matches. Rethrows when it returns.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Catch {
|
||||
|
||||
/**
|
||||
* The {@link Surround#id()} of the surround this handler belongs to; empty for the default.
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Call form only: the exception ends here and the returned value becomes the call's result.
|
||||
*/
|
||||
boolean handle() default false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on every way out.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Finally {
|
||||
|
||||
/**
|
||||
* The {@link Surround#id()} of the surround this handler belongs to; empty for the default.
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* As MixinExtras' {@code @Local}, read at the call; exit handlers see its value at entry. Calls only.
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Local {
|
||||
|
||||
int ordinal() default -1;
|
||||
|
||||
int index() default -1;
|
||||
|
||||
String[] name() default {};
|
||||
|
||||
boolean argsOnly() default false;
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.llamalad7.mixinextras.utils.MixinInternals;
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.transformer.ext.IExtension;
|
||||
import org.spongepowered.asm.mixin.transformer.ext.ITargetClassContext;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class SurroundApplicatorExtension implements IExtension {
|
||||
|
||||
private static final String MIXINEXTRAS_PACKAGE = "com.llamalad7.mixinextras.";
|
||||
private static final Pattern HANDLER_NAME = Pattern.compile("surround\\$[a-z]{3,}[0-9a-f]{3,}\\$.*");
|
||||
|
||||
private final Map<ITargetClassContext, Pending> pending = new WeakHashMap<>();
|
||||
|
||||
private record Pending(List<CallSiteSpec> calls, List<CallSiteSpec> wholes, Set<String> declarers) {}
|
||||
|
||||
private static final class Holder {
|
||||
|
||||
static final SurroundApplicatorExtension INSTANCE = create();
|
||||
|
||||
private static SurroundApplicatorExtension create() {
|
||||
final SurroundApplicatorExtension extension = new SurroundApplicatorExtension();
|
||||
// MixinExtras 0.5.0 internal (pinned by UniMixins 0.3.1); recheck on bump
|
||||
MixinInternals.registerExtension(extension, false);
|
||||
return extension;
|
||||
}
|
||||
}
|
||||
|
||||
static SurroundApplicatorExtension instance() {
|
||||
return Holder.INSTANCE;
|
||||
}
|
||||
|
||||
void declare(ITargetClassContext context, String mixin) {
|
||||
this.pending(context).declarers.add(mixin);
|
||||
}
|
||||
|
||||
void offer(ITargetClassContext context, CallSiteSpec spec) {
|
||||
final Pending pending = this.pending(context);
|
||||
(spec.node() == null ? pending.wholes : pending.calls).add(spec);
|
||||
}
|
||||
|
||||
private Pending pending(ITargetClassContext context) {
|
||||
return this.pending.computeIfAbsent(context, key -> new Pending(new ArrayList<>(), new ArrayList<>(), new HashSet<>()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkActive(MixinEnvironment environment) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preApply(ITargetClassContext context) {
|
||||
// MixinExtras 0.5.0 internal (pinned by UniMixins 0.3.1); recheck on bump
|
||||
final List<IExtension> active = MixinInternals.getExtensions().getActiveExtensions();
|
||||
boolean seen = false;
|
||||
for (int i = 0, n = active.size(); i < n; i++) {
|
||||
final IExtension extension = active.get(i);
|
||||
if (extension == this) {
|
||||
seen = true;
|
||||
} else if (seen && extension.getClass().getName().startsWith(MIXINEXTRAS_PACKAGE)) {
|
||||
MixinInternals.unregisterExtension(this);
|
||||
MixinInternals.registerExtension(this, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postApply(ITargetClassContext context) {
|
||||
final ClassNode targetClass = context.getClassNode();
|
||||
final Pending pending = this.pending.remove(context);
|
||||
checkHandlersRenamed(targetClass, pending == null ? Set.of() : pending.declarers);
|
||||
if (pending == null) {
|
||||
return;
|
||||
}
|
||||
final List<CallSiteSpec> calls = pending.calls;
|
||||
for (int i = 0, n = calls.size(); i < n; i++) {
|
||||
SurroundCallSite.apply(targetClass, calls.get(i));
|
||||
}
|
||||
final List<CallSiteSpec> wholes = pending.wholes;
|
||||
for (int i = 0, n = wholes.size(); i < n; i++) {
|
||||
SurroundCallSite.apply(targetClass, wholes.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void export(MixinEnvironment env, String name, boolean force, ClassNode classNode) {
|
||||
}
|
||||
|
||||
static void checkHandlersRenamed(ClassNode classNode, Set<String> declarers) {
|
||||
for (MethodNode method : classNode.methods) {
|
||||
if (method.visibleAnnotations == null) {
|
||||
continue;
|
||||
}
|
||||
for (AnnotationNode annotation : method.visibleAnnotations) {
|
||||
final String desc = annotation.desc;
|
||||
if (SurroundSpec.CATCH.equals(desc) || SurroundSpec.FINALLY.equals(desc) || SurroundSpec.SKIPPED.equals(desc) || SurroundSpec.RETURN.equals(desc)) {
|
||||
final String mixin = SurroundSpec.mergedBy(method);
|
||||
if (mixin != null && !declarers.contains(mixin)) {
|
||||
throw new IllegalStateException("@Surround " + SurroundBinding.exitRef(method) + " " + SurroundInjector.noSurroundForId(Annotations.<String>getValue(annotation, "value", ""), Set.of()));
|
||||
}
|
||||
} else if (SurroundSpec.SURROUND.equals(desc) && !HANDLER_NAME.matcher(method.name).matches()) {
|
||||
throw new IllegalStateException(method.name + method.desc + " in " + classNode.name + " (" + SurroundSpec.describeOwner(method) + ") carries @Surround but was never parsed as an injector: the InjectionInfo registry lacked @Surround when mixins were applied to this target; SurroundBootstrap.init() must run before any class a @Surround targets is transformed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+543
@@ -0,0 +1,543 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.TypePath;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.FrameNode;
|
||||
import org.spongepowered.asm.lib.tree.IincInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.LabelNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableAnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.lib.tree.VarInsnNode;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
import org.spongepowered.asm.util.Bytecode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
final class SurroundBinding {
|
||||
|
||||
private static final int TOP = -1;
|
||||
private static final String[] SUGARS = {"Local", "Share", "Cancellable"};
|
||||
static final String SUGAR_HINT = ", which a @Surround handler cannot resolve; use @Surround.Carry, or @Surround.Local on a call";
|
||||
|
||||
enum Role {
|
||||
ENTRY(SurroundSpec.SURROUND), CATCH(SurroundSpec.CATCH), FINALLY(SurroundSpec.FINALLY), SKIPPED(SurroundSpec.SKIPPED), RETURN(SurroundSpec.RETURN);
|
||||
|
||||
static final Role[] EXITS = {CATCH, FINALLY, SKIPPED, RETURN};
|
||||
|
||||
final String annotation;
|
||||
|
||||
Role(String annotation) {
|
||||
this.annotation = annotation;
|
||||
}
|
||||
}
|
||||
|
||||
record Load(int slot, Type type) {}
|
||||
|
||||
static final class Handler {
|
||||
|
||||
final MethodNode method;
|
||||
final Role role;
|
||||
final Type[] params;
|
||||
final boolean isStatic;
|
||||
final boolean handles;
|
||||
final int leading;
|
||||
final int localStart;
|
||||
final int carryStart;
|
||||
private final String[] carryIds;
|
||||
int index;
|
||||
Load[] loads;
|
||||
|
||||
Handler(MethodNode method, Role role, int leading) {
|
||||
this.method = method;
|
||||
this.role = role;
|
||||
this.params = Type.getArgumentTypes(method.desc);
|
||||
this.isStatic = Bytecode.isStatic(method);
|
||||
final AnnotationNode caught = role == Role.CATCH ? Annotations.get(method.visibleAnnotations, SurroundSpec.CATCH) : null;
|
||||
this.handles = caught != null && Annotations.<Boolean>getValue(caught, "handle", Boolean.FALSE).booleanValue();
|
||||
this.leading = leading;
|
||||
final int count = this.params.length;
|
||||
int firstLocal = count;
|
||||
int firstCarry = count;
|
||||
int section = 0;
|
||||
String[] ids = null;
|
||||
for (int i = 0; i < count; i++) {
|
||||
final AnnotationNode carry = carry(method, i);
|
||||
final boolean local = local(method, i) != null;
|
||||
if (carry != null && role == Role.ENTRY) {
|
||||
throw this.reject("has @Surround.Carry on " + paramRef(method, i) + "; only the entry handler's own locals can be carried");
|
||||
}
|
||||
if (local && carry != null) {
|
||||
throw this.reject(paramRef(method, i) + " cannot be both @Surround.Local and @Surround.Carry");
|
||||
}
|
||||
if ((local || carry != null) && i < leading) {
|
||||
throw this.reject("cannot mark its " + (role == Role.CATCH ? "caught Throwable" : "result") + " parameter @Surround." + (local ? "Local" : "Carry"));
|
||||
}
|
||||
final int next = carry != null ? 2 : local ? 1 : 0;
|
||||
if (next < section) {
|
||||
throw this.reject(paramRef(method, i) + " is out of order; declare operands first, then @Surround.Local, then @Surround.Carry parameters");
|
||||
}
|
||||
section = next;
|
||||
if (local && firstLocal == count) {
|
||||
firstLocal = i;
|
||||
}
|
||||
if (carry != null) {
|
||||
if (ids == null) {
|
||||
firstCarry = i;
|
||||
ids = new String[count - i];
|
||||
}
|
||||
ids[i - firstCarry] = Annotations.<String>getValue(carry, "value", "");
|
||||
}
|
||||
}
|
||||
this.localStart = Math.min(firstLocal, firstCarry);
|
||||
this.carryStart = firstCarry;
|
||||
this.carryIds = ids;
|
||||
}
|
||||
|
||||
Type caught() {
|
||||
return this.params[0];
|
||||
}
|
||||
|
||||
String ref() {
|
||||
return this.role == Role.ENTRY ? "entry handler" : exitRef(this.method);
|
||||
}
|
||||
|
||||
SurroundRejection reject(String message) {
|
||||
return new SurroundRejection(this.ref() + " " + message);
|
||||
}
|
||||
}
|
||||
|
||||
static final class Exits {
|
||||
|
||||
final Handler[] handlers;
|
||||
final int catches;
|
||||
final Handler finallyHandler;
|
||||
final Handler skipped;
|
||||
final Handler returnHandler;
|
||||
final boolean handles;
|
||||
final int skipSlot;
|
||||
final Type[] carried;
|
||||
final int argsSize;
|
||||
|
||||
Exits(Handler[] handlers, int skipSlot, Type[] carried) {
|
||||
this.handlers = handlers;
|
||||
int catches = 0;
|
||||
Handler finallyHandler = null;
|
||||
Handler skipped = null;
|
||||
Handler returnHandler = null;
|
||||
boolean handles = false;
|
||||
int argsSize = 0;
|
||||
for (int i = 0; i < handlers.length; i++) {
|
||||
final Handler handler = handlers[i];
|
||||
handler.index = i;
|
||||
handles |= handler.handles;
|
||||
argsSize = Math.max(argsSize, (handler.isStatic ? 0 : 1) + Bytecode.getArgsSize(handler.params));
|
||||
switch (handler.role) {
|
||||
case CATCH -> catches++;
|
||||
case FINALLY -> finallyHandler = handler;
|
||||
case SKIPPED -> skipped = handler;
|
||||
case RETURN -> returnHandler = handler;
|
||||
}
|
||||
}
|
||||
this.catches = catches;
|
||||
this.finallyHandler = finallyHandler;
|
||||
this.skipped = skipped;
|
||||
this.returnHandler = returnHandler;
|
||||
this.handles = handles;
|
||||
this.skipSlot = skipSlot;
|
||||
this.carried = carried;
|
||||
this.argsSize = argsSize;
|
||||
}
|
||||
}
|
||||
|
||||
private SurroundBinding() {
|
||||
}
|
||||
|
||||
static Type[] bind(MethodNode entry, int[] kinds, Handler[] handlers) {
|
||||
checkNoParameterStores(entry);
|
||||
final List<Carried> carried = carriedLocals(entry, kinds);
|
||||
final Type[] slots = new Type[entry.maxLocals];
|
||||
for (Handler exit : handlers) {
|
||||
final Load[] loads = new Load[exit.params.length - exit.carryStart];
|
||||
for (int i = exit.carryStart; i < exit.params.length; i++) {
|
||||
loads[i - exit.carryStart] = resolve(carried, slots, exit, i);
|
||||
}
|
||||
exit.loads = loads;
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
static int skipSlot(MethodNode entry, int[] kinds) {
|
||||
final List<LocalVariableAnnotationNode> annotations = entry.visibleLocalVariableAnnotations;
|
||||
int slot = -1;
|
||||
for (int a = 0, n = annotations == null ? 0 : annotations.size(); a < n; a++) {
|
||||
final LocalVariableAnnotationNode annotation = annotations.get(a);
|
||||
if (!SurroundSpec.SKIP.equals(annotation.desc)) {
|
||||
continue;
|
||||
}
|
||||
if (slot != -1) {
|
||||
throw new SurroundRejection("declares two @Surround.Skip locals; there can only be one");
|
||||
}
|
||||
slot = annotatedSlot(entry, annotation, 0);
|
||||
checkLocalAnnotation(entry, kinds, "@Surround.Skip", annotation, slot, "");
|
||||
if (kinds[slot] != Type.INT) {
|
||||
throw new SurroundRejection("has a @Surround.Skip local that is " + withArticle(kinds[slot]) + "; it must be boolean or int-like");
|
||||
}
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
private static AnnotationNode carry(MethodNode method, int index) {
|
||||
final List<AnnotationNode> invisible = parameter(method.invisibleParameterAnnotations, index);
|
||||
for (int i = 0, n = invisible == null ? 0 : invisible.size(); i < n; i++) {
|
||||
final String name = Type.getType(invisible.get(i).desc).getClassName();
|
||||
for (String sugar : SUGARS) {
|
||||
if (name.endsWith("mixinextras.sugar." + sugar)) {
|
||||
throw new SurroundRejection(paramRef(method, index) + " of '" + method.name + "' is annotated @" + sugar + SUGAR_HINT);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Annotations.get(parameter(method.visibleParameterAnnotations, index), SurroundSpec.CARRY);
|
||||
}
|
||||
|
||||
static AnnotationNode local(MethodNode method, int index) {
|
||||
return Annotations.get(parameter(method.visibleParameterAnnotations, index), SurroundSpec.LOCAL);
|
||||
}
|
||||
|
||||
private static List<AnnotationNode> parameter(List<AnnotationNode>[] table, int index) {
|
||||
return table == null || index >= table.length ? null : table[index];
|
||||
}
|
||||
|
||||
static String paramRef(MethodNode method, int index) {
|
||||
final String name = paramName(method, index);
|
||||
return name == null || name.isEmpty() ? "parameter " + index : "parameter " + index + " ('" + name + "')";
|
||||
}
|
||||
|
||||
static String paramName(MethodNode method, int index) {
|
||||
if (method.localVariables == null) {
|
||||
return null;
|
||||
}
|
||||
final int slot = (Bytecode.isStatic(method) ? 0 : 1) + Bytecode.getArgsSize(Type.getArgumentTypes(method.desc), 0, index);
|
||||
LocalVariableNode earliest = null;
|
||||
int earliestPos = Integer.MAX_VALUE;
|
||||
for (LocalVariableNode local : method.localVariables) {
|
||||
if (local.index != slot) {
|
||||
continue;
|
||||
}
|
||||
final int pos = method.instructions.indexOf(local.start);
|
||||
if (pos < earliestPos) {
|
||||
earliestPos = pos;
|
||||
earliest = local;
|
||||
}
|
||||
}
|
||||
return earliest == null ? null : earliest.name;
|
||||
}
|
||||
|
||||
static AbstractInsnNode soleReturn(MethodNode entry) {
|
||||
AbstractInsnNode sole = null;
|
||||
AbstractInsnNode last = null;
|
||||
int returns = 0;
|
||||
for (AbstractInsnNode insn = entry.instructions.getFirst(); insn != null; insn = insn.getNext()) {
|
||||
if (insn.getOpcode() < 0) {
|
||||
continue;
|
||||
}
|
||||
last = insn;
|
||||
if (insn.getOpcode() == Opcodes.RETURN) {
|
||||
sole = insn;
|
||||
returns++;
|
||||
}
|
||||
}
|
||||
if (returns != 1 || sole != last) {
|
||||
throw new SurroundRejection("must reach its end to hand over to the body; restructure it so every path falls through to its end, with no early return, throw or endless loop");
|
||||
}
|
||||
return sole;
|
||||
}
|
||||
|
||||
private static Load resolve(List<Carried> carried, Type[] slots, Handler exit, int index) {
|
||||
final Type type = exit.params[index];
|
||||
final String id = exit.carryIds[index - exit.carryStart];
|
||||
final int kind = kindOf(type);
|
||||
final boolean named = !id.isEmpty();
|
||||
boolean idDeclared = false;
|
||||
int matches = 0;
|
||||
Carried match = null;
|
||||
for (int i = 0, n = carried.size(); i < n; i++) {
|
||||
final Carried candidate = carried.get(i);
|
||||
if (named && !id.equals(candidate.id())) {
|
||||
continue;
|
||||
}
|
||||
idDeclared = true;
|
||||
if (candidate.kind() == kind) {
|
||||
matches++;
|
||||
match = candidate;
|
||||
}
|
||||
}
|
||||
final String param = paramRef(exit.method, index);
|
||||
if (named && !idDeclared) {
|
||||
throw exit.reject(param + " asks for the carried local '" + id + "', which the entry handler does not declare" + declaredList(carried));
|
||||
}
|
||||
if (matches == 0) {
|
||||
throw exit.reject(param + " is " + type.getClassName() + " (" + withArticle(kind) + "), which matches no carried local of that kind" + declaredList(carried));
|
||||
}
|
||||
if (matches > 1) {
|
||||
throw exit.reject(param + " is " + type.getClassName() + " (" + withArticle(kind) + "), which matches " + matches + " carried locals of that kind; name them with @Surround.Carry(\"...\") on both sides");
|
||||
}
|
||||
final Type existing = slots[match.slot()];
|
||||
if (existing != null && !existing.equals(type)) {
|
||||
throw exit.reject(param + " reads carried local " + match + " as " + type.getClassName() + ", but another exit handler reads it as " + existing.getClassName());
|
||||
}
|
||||
slots[match.slot()] = type;
|
||||
return new Load(match.slot(), type);
|
||||
}
|
||||
|
||||
private static List<Carried> carriedLocals(MethodNode entry, int[] kinds) {
|
||||
final List<Carried> carried = new ArrayList<>();
|
||||
final List<LocalVariableAnnotationNode> annotations = entry.visibleLocalVariableAnnotations;
|
||||
for (int a = 0, n = annotations == null ? 0 : annotations.size(); a < n; a++) {
|
||||
final LocalVariableAnnotationNode annotation = annotations.get(a);
|
||||
if (!SurroundSpec.CARRY.equals(annotation.desc)) {
|
||||
continue;
|
||||
}
|
||||
final int slot = annotatedSlot(entry, annotation, 0);
|
||||
final String id = Annotations.<String>getValue(annotation, "value", "");
|
||||
checkLocalAnnotation(entry, kinds, "@Surround.Carry", annotation, slot, id);
|
||||
if (!id.isEmpty()) {
|
||||
for (int i = 0, count = carried.size(); i < count; i++) {
|
||||
if (id.equals(carried.get(i).id())) {
|
||||
throw new SurroundRejection("declares two @Surround.Carry locals named '" + id + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
carried.add(new Carried(slot, kinds[slot], id));
|
||||
}
|
||||
return carried;
|
||||
}
|
||||
|
||||
// FML's deobf remapper (ASM LocalVariablesSorter) corrupts annotation indices of long/double locals; the LVT stays right.
|
||||
private static int annotatedSlot(MethodNode entry, LocalVariableAnnotationNode annotation, int range) {
|
||||
final int raw = annotation.index.get(range).intValue();
|
||||
final LabelNode start = annotation.start.get(range);
|
||||
final LabelNode end = annotation.end.get(range);
|
||||
boolean any = false;
|
||||
boolean rawWide = false;
|
||||
boolean rawFound = false;
|
||||
int wide = -1;
|
||||
int wideCount = 0;
|
||||
for (int i = 0, n = entry.localVariables == null ? 0 : entry.localVariables.size(); i < n; i++) {
|
||||
final LocalVariableNode local = entry.localVariables.get(i);
|
||||
if (local.start != start || local.end != end) {
|
||||
continue;
|
||||
}
|
||||
any = true;
|
||||
final boolean isWide = Type.getType(local.desc).getSize() == 2;
|
||||
if (isWide) {
|
||||
wide = local.index;
|
||||
wideCount++;
|
||||
}
|
||||
if (local.index == raw) {
|
||||
rawFound = true;
|
||||
rawWide = isWide;
|
||||
}
|
||||
}
|
||||
final int slot = !any || rawFound && !(rawWide && wideCount > 1) ? raw : wideCount == 1 && !rawFound ? wide : -1;
|
||||
if (slot < 0) {
|
||||
throw new SurroundRejection("has a long or double annotated local that shares its scope with another; initialize it at its declaration");
|
||||
}
|
||||
if (slot >= entry.maxLocals) {
|
||||
throw new SurroundRejection("has an annotated local outside its method's locals (slot " + slot + " of " + entry.maxLocals + "); compile with local variable tables");
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
private static void checkLocalAnnotation(MethodNode entry, int[] kinds, String what, LocalVariableAnnotationNode annotation, int slot, String id) {
|
||||
final String local = id.isEmpty() ? "in slot " + slot : "named '" + id + "'";
|
||||
final TypePath path = annotation.typePath;
|
||||
for (int i = 0, length = path == null ? 0 : path.getLength(); i < length; i++) {
|
||||
if (path.getStep(i) != TypePath.ARRAY_ELEMENT) {
|
||||
throw new SurroundRejection("has a " + what + " " + local + " inside the local's type (" + path + "); " + what + " must annotate the local itself, not part of its type");
|
||||
}
|
||||
}
|
||||
for (int i = 1, count = annotation.index.size(); i < count; i++) {
|
||||
if (annotatedSlot(entry, annotation, i) != slot) {
|
||||
throw new SurroundRejection("has a " + what + " local " + local + " recorded in several slots " + annotation.index + "; initialize it at its declaration");
|
||||
}
|
||||
}
|
||||
LabelNode end = annotation.end.get(0);
|
||||
for (int i = 1, n = annotation.end.size(); i < n; i++) {
|
||||
if (entry.instructions.indexOf(annotation.end.get(i)) > entry.instructions.indexOf(end)) {
|
||||
end = annotation.end.get(i);
|
||||
}
|
||||
}
|
||||
for (AbstractInsnNode insn = end.getNext(); insn != null; insn = insn.getNext()) {
|
||||
if (insn.getOpcode() >= 0 && insn.getOpcode() != Opcodes.RETURN) {
|
||||
throw new SurroundRejection("has a " + what + " local " + local + " that goes out of scope before the entry handler ends; declare it in the entry handler's outermost block");
|
||||
}
|
||||
}
|
||||
if (kinds[slot] == TOP) {
|
||||
throw new SurroundRejection("has a " + what + " local " + local + " that is not definitely assigned when the entry handler ends; initialize it at its declaration");
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkNoParameterStores(MethodNode entry) {
|
||||
final int limit = Bytecode.getFirstNonArgLocalIndex(entry);
|
||||
for (AbstractInsnNode insn = entry.instructions.getFirst(); insn != null; insn = insn.getNext()) {
|
||||
final int var;
|
||||
if (insn instanceof VarInsnNode varInsn && storeKind(insn.getOpcode()) != -1) {
|
||||
var = varInsn.var;
|
||||
} else if (insn instanceof IincInsnNode iinc) {
|
||||
var = iinc.var;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (var < limit) {
|
||||
throw new SurroundRejection("must not assign its parameters; use @ModifyVariable or @WrapMethod to change arguments");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int[] kindsAtEnd(MethodNode entry, AbstractInsnNode soleReturn) {
|
||||
final int maxLocals = entry.maxLocals;
|
||||
int[] vars = initialVars(entry);
|
||||
int count = vars.length;
|
||||
int[] state = toSlots(vars, count, maxLocals);
|
||||
for (AbstractInsnNode insn = entry.instructions.getFirst(); insn != soleReturn; insn = insn.getNext()) {
|
||||
if (insn instanceof FrameNode frame) {
|
||||
final int locals = frame.local == null ? 0 : frame.local.size();
|
||||
switch (frame.type) {
|
||||
case Opcodes.F_NEW, Opcodes.F_FULL, Opcodes.F_APPEND -> {
|
||||
if (frame.type != Opcodes.F_APPEND) {
|
||||
count = 0;
|
||||
}
|
||||
if (count + locals > vars.length) {
|
||||
vars = Arrays.copyOf(vars, count + locals);
|
||||
}
|
||||
for (int i = 0; i < locals; i++) {
|
||||
vars[count++] = frameKind(frame.local.get(i));
|
||||
}
|
||||
}
|
||||
case Opcodes.F_CHOP -> count -= locals;
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
state = toSlots(vars, count, maxLocals);
|
||||
} else if (insn instanceof VarInsnNode var && storeKind(insn.getOpcode()) != -1) {
|
||||
store(state, var.var, storeKind(insn.getOpcode()));
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
private static void store(int[] state, int var, int kind) {
|
||||
if (var > 0 && wide(state[var - 1])) {
|
||||
state[var - 1] = TOP;
|
||||
}
|
||||
state[var] = kind;
|
||||
if (wide(kind) && var + 1 < state.length) {
|
||||
state[var + 1] = TOP;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean wide(int kind) {
|
||||
return kind == Type.LONG || kind == Type.DOUBLE;
|
||||
}
|
||||
|
||||
private static int[] initialVars(MethodNode entry) {
|
||||
final Type[] args = Type.getArgumentTypes(entry.desc);
|
||||
final int receiver = Bytecode.isStatic(entry) ? 0 : 1;
|
||||
final int[] vars = new int[receiver + args.length];
|
||||
if (receiver != 0) {
|
||||
vars[0] = Type.OBJECT;
|
||||
}
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
vars[receiver + i] = kindOf(args[i]);
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
private static int frameKind(Object entry) {
|
||||
if (entry instanceof String || Opcodes.NULL.equals(entry) || Opcodes.UNINITIALIZED_THIS.equals(entry)) {
|
||||
return Type.OBJECT;
|
||||
} else if (Opcodes.INTEGER.equals(entry)) {
|
||||
return Type.INT;
|
||||
} else if (Opcodes.FLOAT.equals(entry)) {
|
||||
return Type.FLOAT;
|
||||
} else if (Opcodes.LONG.equals(entry)) {
|
||||
return Type.LONG;
|
||||
} else if (Opcodes.DOUBLE.equals(entry)) {
|
||||
return Type.DOUBLE;
|
||||
}
|
||||
return TOP;
|
||||
}
|
||||
|
||||
private static int[] toSlots(int[] vars, int count, int maxLocals) {
|
||||
final int[] slots = new int[maxLocals];
|
||||
Arrays.fill(slots, TOP);
|
||||
int slot = 0;
|
||||
for (int i = 0; i < count && slot < maxLocals; i++) {
|
||||
slots[slot] = vars[i];
|
||||
slot += wide(vars[i]) ? 2 : 1;
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
private static int storeKind(int opcode) {
|
||||
return switch (opcode) {
|
||||
case Opcodes.ISTORE -> Type.INT;
|
||||
case Opcodes.LSTORE -> Type.LONG;
|
||||
case Opcodes.FSTORE -> Type.FLOAT;
|
||||
case Opcodes.DSTORE -> Type.DOUBLE;
|
||||
case Opcodes.ASTORE -> Type.OBJECT;
|
||||
default -> -1;
|
||||
};
|
||||
}
|
||||
|
||||
static int kindOf(Type type) {
|
||||
return switch (type.getSort()) {
|
||||
case Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT -> Type.INT;
|
||||
case Type.LONG -> Type.LONG;
|
||||
case Type.FLOAT -> Type.FLOAT;
|
||||
case Type.DOUBLE -> Type.DOUBLE;
|
||||
default -> Type.OBJECT;
|
||||
};
|
||||
}
|
||||
|
||||
private static String kindDescription(int kind) {
|
||||
return switch (kind) {
|
||||
case Type.INT -> "int-like value";
|
||||
case Type.LONG -> "long";
|
||||
case Type.FLOAT -> "float";
|
||||
case Type.DOUBLE -> "double";
|
||||
default -> "reference";
|
||||
};
|
||||
}
|
||||
|
||||
private static String withArticle(int kind) {
|
||||
return (kind == Type.INT ? "an " : "a ") + kindDescription(kind);
|
||||
}
|
||||
|
||||
private static String declaredList(List<Carried> carried) {
|
||||
final StringJoiner all = new StringJoiner(", ", "; the entry handler carries ", "").setEmptyValue("; the entry handler carries nothing");
|
||||
for (Carried local : carried) {
|
||||
all.add(local.toString());
|
||||
}
|
||||
return all.toString();
|
||||
}
|
||||
|
||||
static String exitRef(MethodNode exit) {
|
||||
return "exit handler '" + exit.name + "'";
|
||||
}
|
||||
|
||||
private record Carried(int slot, int kind, String id) {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (this.id.isEmpty() ? "an unnamed " + kindDescription(this.kind) : "'" + this.id + "', " + withArticle(this.kind)) + " in slot " + this.slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.launch.GlobalProperties;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionInfo;
|
||||
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
|
||||
|
||||
public final class SurroundBootstrap {
|
||||
|
||||
private SurroundBootstrap() {
|
||||
}
|
||||
|
||||
private static final class Registration {
|
||||
|
||||
static {
|
||||
InjectionInfo.register(SurroundInjectionInfo.class);
|
||||
}
|
||||
|
||||
static void ensure() {
|
||||
}
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
Registration.ensure();
|
||||
if (GlobalProperties.get(GlobalProperties.Keys.INIT) != null && MixinEnvironment.getDefaultEnvironment().getActiveTransformer() instanceof IMixinTransformer) {
|
||||
SurroundApplicatorExtension.instance();
|
||||
}
|
||||
}
|
||||
}
|
||||
+605
@@ -0,0 +1,605 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Exits;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Handler;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Load;
|
||||
import com.llamalad7.mixinextras.utils.ASMUtils;
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.FrameNode;
|
||||
import org.spongepowered.asm.lib.tree.IincInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.InsnList;
|
||||
import org.spongepowered.asm.lib.tree.InsnNode;
|
||||
import org.spongepowered.asm.lib.tree.JumpInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.LabelNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.lib.tree.TryCatchBlockNode;
|
||||
import org.spongepowered.asm.lib.tree.TypeInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.VarInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.analysis.Analyzer;
|
||||
import org.spongepowered.asm.lib.tree.analysis.AnalyzerException;
|
||||
import org.spongepowered.asm.lib.tree.analysis.BasicInterpreter;
|
||||
import org.spongepowered.asm.lib.tree.analysis.BasicValue;
|
||||
import org.spongepowered.asm.lib.tree.analysis.Frame;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode;
|
||||
import org.spongepowered.asm.service.MixinService;
|
||||
import org.spongepowered.asm.util.Bytecode;
|
||||
import org.spongepowered.asm.util.Bytecode.Visibility;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
final class SurroundCallSite {
|
||||
|
||||
private static final String REGION = "surround:region";
|
||||
private static final Type[] NOTHING = new Type[0];
|
||||
|
||||
record Region(LabelNode start, LabelNode end) {}
|
||||
|
||||
private SurroundCallSite() {
|
||||
}
|
||||
|
||||
static Type[] operands(MethodInsnNode call, String targetClass) {
|
||||
final Type[] args = Type.getArgumentTypes(call.desc);
|
||||
if (call.getOpcode() == Opcodes.INVOKESTATIC) {
|
||||
return args;
|
||||
}
|
||||
final Type[] all = new Type[args.length + 1];
|
||||
all[0] = Type.getObjectType(call.getOpcode() == Opcodes.INVOKESPECIAL ? targetClass : call.owner);
|
||||
System.arraycopy(args, 0, all, 1, args.length);
|
||||
return all;
|
||||
}
|
||||
|
||||
static void apply(ClassNode classNode, CallSiteSpec spec) {
|
||||
final InjectionNode node = spec.node();
|
||||
if (node == null) {
|
||||
final MethodNode target = spec.target();
|
||||
final MethodNode body = relocate(classNode, target);
|
||||
final MethodInsnNode call = (MethodInsnNode) ASMUtils.getInvokeInstruction(classNode, body);
|
||||
target.instructions.add(call);
|
||||
target.instructions.add(new InsnNode(spec.result().getOpcode(Opcodes.IRETURN)));
|
||||
final Type[] args = operands(call, classNode.name);
|
||||
final int[] argSlots = new int[args.length];
|
||||
for (int i = 1; i < args.length; i++) {
|
||||
argSlots[i] = argSlots[i - 1] + args[i - 1].getSize();
|
||||
}
|
||||
emit(classNode, spec, target, call, null, Bytecode.isStatic(target) ? 0 : 1, argSlots);
|
||||
return;
|
||||
}
|
||||
final AbstractInsnNode champion = node.getCurrentTarget();
|
||||
final Region inner = node.<Region>getDecoration(REGION);
|
||||
final MethodNode owner = champion == null ? null : owner(classNode, inner == null ? champion : inner.start());
|
||||
if (owner == null) {
|
||||
MixinService.getService().getLogger("mixin").warn(spec.description() + " skipped: the surrounded call was removed by another injector");
|
||||
return;
|
||||
}
|
||||
if (!spec.entryDesc().equals(spec.entry().desc)) {
|
||||
throw cannotFollow(spec, "the entry handler's descriptor changed from " + spec.entryDesc() + " to " + spec.entry().desc + " after injection", null);
|
||||
}
|
||||
if (!(champion instanceof MethodInsnNode call)) {
|
||||
throw cannotFollow(spec, "the call was replaced by " + Bytecode.describeNode(champion, false) + ", which is not a method call", null);
|
||||
}
|
||||
final int offset = node.isReplaced() && call.getOpcode() != Opcodes.INVOKESTATIC ? 1 : 0;
|
||||
checkOperands(spec, call, operands(call, classNode.name), offset);
|
||||
final Exits exits = spec.exits();
|
||||
final Type result = spec.result();
|
||||
final Type actual = Type.getReturnType(call.desc);
|
||||
if ((exits.handles || exits.skipSlot >= 0 || exits.returnHandler != null) && (actual.getSize() != result.getSize() || actual.getSize() > 0 && SurroundBinding.kindOf(actual) != SurroundBinding.kindOf(result))) {
|
||||
throw cannotFollow(spec, "the call was replaced by " + Bytecode.describeNode(call, false) + ", which returns " + actual.getClassName() + " where the surround supplies or replaces a " + result.getClassName(), null);
|
||||
}
|
||||
emit(classNode, spec, owner, call, inner, offset, null);
|
||||
}
|
||||
|
||||
private static void emit(ClassNode classNode, CallSiteSpec spec, MethodNode owner, MethodInsnNode call, Region inner, int offset, int[] argSlots) {
|
||||
final boolean slots = argSlots != null;
|
||||
final Type[] args = operands(call, classNode.name);
|
||||
final Exits exits = spec.exits();
|
||||
final boolean skips = exits.skipSlot >= 0;
|
||||
final Handler returnHandler = exits.returnHandler;
|
||||
final Type result = spec.result();
|
||||
final Type actual = Type.getReturnType(call.desc);
|
||||
|
||||
final MethodNode entry = spec.entry();
|
||||
final Type[] operands = spec.operands();
|
||||
final int argBase = Bytecode.isStatic(spec.target()) ? 0 : 1;
|
||||
final Type[] entryParams = Type.getArgumentTypes(entry.desc);
|
||||
final int entryOperands = entryParams.length - spec.entryLocals().length;
|
||||
final int firstNonArg = argBase + Bytecode.getArgsSize(entryParams);
|
||||
final MethodNode inlined = inlineable(entry);
|
||||
|
||||
final boolean restoresBelow = exits.handles || !inlined.tryCatchBlocks.isEmpty();
|
||||
final boolean relocated = owner != spec.target();
|
||||
owner.maxLocals = Math.max(Math.max(owner.maxLocals, spec.target().maxLocals), usedLocals(owner));
|
||||
int next = owner.maxLocals;
|
||||
final AbstractInsnNode anchor = inner == null ? call : inner.start();
|
||||
final Frame<BasicValue> frame = !slots && (restoresBelow || relocated && hasLocals(spec)) ? frameAt(owner, anchor, spec) : null;
|
||||
final int shift = relocated ? Bytecode.getFirstNonArgLocalIndex(owner) - Bytecode.getFirstNonArgLocalIndex(spec.target()) : 0;
|
||||
|
||||
final Type[] below = restoresBelow ? below(spec, frame, args.length) : NOTHING;
|
||||
final int spillFrom = slots || restoresBelow || skips ? 0 : offset;
|
||||
final boolean spill = slots || restoresBelow || skips || args.length > offset && (entryOperands > 0 || prefixed(spec));
|
||||
final int[] spillSlots = slots ? argSlots : new int[args.length];
|
||||
if (spill && !slots) {
|
||||
for (int i = spillFrom; i < args.length; i++) {
|
||||
spillSlots[i] = next;
|
||||
next += args[i].getSize();
|
||||
}
|
||||
}
|
||||
final int[] belowSlots = new int[below.length];
|
||||
for (int i = 0; i < below.length; i++) {
|
||||
belowSlots[i] = next;
|
||||
next += below[i].getSize();
|
||||
}
|
||||
final int entryBase = next;
|
||||
final int delta = entryBase - firstNonArg;
|
||||
final int excSlot = entryBase + Math.max(0, inlined.maxLocals - firstNonArg);
|
||||
final int resSlot = excSlot + 1;
|
||||
final boolean storesResult = returnHandler != null && result.getSize() > 0;
|
||||
|
||||
final int[] entryLocals = localSlots(spec, owner, anchor, frame, shift, spec.entryLocals(), entryParams, entryOperands);
|
||||
remapLocals(inlined, argBase, firstNonArg, entryParams, entryOperands, spillSlots, offset, entryLocals, delta);
|
||||
final Handler[] handlers = exits.handlers;
|
||||
final int[][] locals = new int[handlers.length][];
|
||||
for (Handler exit : handlers) {
|
||||
locals[exit.index] = localSlots(spec, owner, anchor, frame, shift, spec.locals()[exit.index], exit.params, exit.localStart);
|
||||
}
|
||||
final Emitter emitter = new Emitter(classNode, exits, operands, Arrays.copyOfRange(spillSlots, offset, offset + operands.length), locals, delta);
|
||||
|
||||
final LabelNode sStart = new LabelNode();
|
||||
final LabelNode tStart = inner == null || skips ? new LabelNode() : inner.start();
|
||||
final LabelNode tEnd = inner == null ? new LabelNode() : inner.end();
|
||||
final LabelNode sEnd = new LabelNode();
|
||||
final LabelNode join = skips ? new LabelNode() : null;
|
||||
|
||||
final InsnList head = new InsnList();
|
||||
head.add(sStart);
|
||||
if (spill && !slots) {
|
||||
for (int i = args.length - 1; i >= spillFrom; i--) {
|
||||
final int j = i - offset;
|
||||
if (j >= 0 && j < operands.length && operands[j].getSort() >= Type.ARRAY && !args[i].equals(operands[j])) {
|
||||
head.add(new TypeInsnNode(Opcodes.CHECKCAST, operands[j].getInternalName()));
|
||||
}
|
||||
head.add(new VarInsnNode(args[i].getOpcode(Opcodes.ISTORE), spillSlots[i]));
|
||||
}
|
||||
for (int i = below.length - 1; i >= 0; i--) {
|
||||
head.add(new VarInsnNode(below[i].getOpcode(Opcodes.ISTORE), belowSlots[i]));
|
||||
}
|
||||
}
|
||||
head.add(inlined.instructions);
|
||||
if (skips) {
|
||||
final LabelNode callPath = new LabelNode();
|
||||
head.add(new VarInsnNode(Opcodes.ILOAD, exits.skipSlot + delta));
|
||||
head.add(new JumpInsnNode(Opcodes.IFEQ, callPath));
|
||||
head.add(tStart);
|
||||
loadBelow(head, below, belowSlots);
|
||||
emitter.callSkipped(head);
|
||||
head.add(new JumpInsnNode(Opcodes.GOTO, join));
|
||||
head.add(callPath);
|
||||
}
|
||||
if (spill) {
|
||||
loadBelow(head, below, belowSlots);
|
||||
for (int i = spillFrom; i < args.length; i++) {
|
||||
head.add(new VarInsnNode(args[i].getOpcode(Opcodes.ILOAD), spillSlots[i]));
|
||||
}
|
||||
}
|
||||
if (inner == null && !skips) {
|
||||
head.add(tStart);
|
||||
}
|
||||
owner.instructions.insertBefore(anchor, head);
|
||||
|
||||
final InsnList tail = new InsnList();
|
||||
if (inner == null) {
|
||||
tail.add(tEnd);
|
||||
}
|
||||
final LabelNode retStart = returnHandler == null ? null : new LabelNode();
|
||||
final LabelNode retEnd = returnHandler == null ? null : new LabelNode();
|
||||
if (returnHandler != null) {
|
||||
tail.add(retStart);
|
||||
if (result.getSize() > 0) {
|
||||
if (result.getSort() >= Type.ARRAY && !actual.equals(result)) {
|
||||
tail.add(new TypeInsnNode(Opcodes.CHECKCAST, result.getInternalName()));
|
||||
}
|
||||
tail.add(new VarInsnNode(result.getOpcode(Opcodes.ISTORE), resSlot));
|
||||
}
|
||||
emitter.callReturn(tail, result, resSlot);
|
||||
tail.add(retEnd);
|
||||
}
|
||||
if (skips) {
|
||||
tail.add(join);
|
||||
}
|
||||
emitter.callFinally(tail);
|
||||
tail.add(new JumpInsnNode(Opcodes.GOTO, sEnd));
|
||||
final AbstractInsnNode beforeHandlers = tail.getLast();
|
||||
|
||||
final List<TryCatchBlockNode> blocks = new ArrayList<>(inlined.tryCatchBlocks);
|
||||
emitter.emitHandlers(tail, blocks, tStart, tEnd, retStart, retEnd, excSlot, below, belowSlots, sEnd);
|
||||
final AbstractInsnNode firstHandler = beforeHandlers.getNext();
|
||||
tail.add(sEnd);
|
||||
owner.instructions.insert(inner == null ? call : inner.end(), tail);
|
||||
|
||||
owner.tryCatchBlocks.addAll(insertionIndex(owner, sStart, sEnd), blocks);
|
||||
owner.maxLocals = Math.max(owner.maxLocals, storesResult ? resSlot + result.getSize() : excSlot + 1);
|
||||
owner.maxStack = Math.max(owner.maxStack, spec.target().maxStack) + Math.max(inlined.maxStack, exits.argsSize + 2);
|
||||
if (slots) {
|
||||
final LabelNode end = new LabelNode();
|
||||
owner.instructions.add(end);
|
||||
final List<LocalVariableNode> names = new ArrayList<>();
|
||||
nameParameters(names, classNode, spec, argBase, entryOperands, sStart, end);
|
||||
nameEntryLocals(names, inlined, owner.instructions, exits.carried, firstNonArg, delta, end);
|
||||
if (firstHandler != null) {
|
||||
names.add(new LocalVariableNode("surround$error", SurroundSpec.THROWABLE_DESC, null, (LabelNode) firstHandler, end, excSlot));
|
||||
}
|
||||
if (storesResult) {
|
||||
names.add(new LocalVariableNode("surround$result", result.getDescriptor(), null, retStart, retEnd, resSlot));
|
||||
}
|
||||
owner.localVariables = names;
|
||||
} else {
|
||||
spec.node().decorate(REGION, new Region(sStart, sEnd));
|
||||
}
|
||||
}
|
||||
|
||||
private static void nameParameters(List<LocalVariableNode> names, ClassNode classNode, CallSiteSpec spec, int argBase, int entryOperands, LabelNode start, LabelNode end) {
|
||||
final Type[] params = spec.operands();
|
||||
final MethodNode entry = spec.entry();
|
||||
if (argBase > 0) {
|
||||
names.add(new LocalVariableNode("this", Type.getObjectType(classNode.name).getDescriptor(), null, start, end, 0));
|
||||
}
|
||||
for (int i = 0, slot = argBase; i < params.length; slot += params[i++].getSize()) {
|
||||
final String name = i < entryOperands ? SurroundBinding.paramName(entry, i) : null;
|
||||
names.add(new LocalVariableNode(name != null ? name : "arg" + i, params[i].getDescriptor(), null, start, end, slot));
|
||||
}
|
||||
}
|
||||
|
||||
private static void nameEntryLocals(List<LocalVariableNode> names, MethodNode inlined, InsnList insns, Type[] carried, int firstNonArg, int delta, LabelNode end) {
|
||||
final LocalVariableNode[] furthest = new LocalVariableNode[carried.length];
|
||||
for (LocalVariableNode local : inlined.localVariables) {
|
||||
if (!isCarried(carried, local.index)) {
|
||||
continue;
|
||||
}
|
||||
final LocalVariableNode current = furthest[local.index];
|
||||
if (current == null || insns.indexOf(local.end) > insns.indexOf(current.end)) {
|
||||
furthest[local.index] = local;
|
||||
}
|
||||
}
|
||||
for (LocalVariableNode local : inlined.localVariables) {
|
||||
if (local.index < firstNonArg) {
|
||||
continue;
|
||||
}
|
||||
if (isCarried(carried, local.index) && local == furthest[local.index]) {
|
||||
final Type type = carried[local.index];
|
||||
names.add(new LocalVariableNode(local.name, type.getSort() >= Type.ARRAY ? local.desc : type.getDescriptor(), null, local.start, end, local.index + delta));
|
||||
} else {
|
||||
local.index += delta;
|
||||
names.add(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCarried(Type[] carried, int slot) {
|
||||
return slot < carried.length && carried[slot] != null;
|
||||
}
|
||||
|
||||
private static MethodNode inlineable(MethodNode entry) {
|
||||
final MethodNode copy = new MethodNode(entry.access, entry.name, entry.desc, null, null);
|
||||
entry.accept(copy);
|
||||
for (AbstractInsnNode insn = copy.instructions.getFirst(); insn != null; ) {
|
||||
final AbstractInsnNode next = insn.getNext();
|
||||
if (insn instanceof FrameNode) {
|
||||
copy.instructions.remove(insn);
|
||||
}
|
||||
insn = next;
|
||||
}
|
||||
AbstractInsnNode last = copy.instructions.getLast();
|
||||
while (last.getOpcode() < 0) {
|
||||
last = last.getPrevious();
|
||||
}
|
||||
copy.instructions.remove(last);
|
||||
return copy;
|
||||
}
|
||||
|
||||
private static MethodNode relocate(ClassNode classNode, MethodNode target) {
|
||||
final MethodNode body = new MethodNode(target.access & ~Opcodes.ACC_SYNCHRONIZED, unique(classNode, target.name + "$surround"), target.desc, null, null);
|
||||
Bytecode.setVisibility(body, Visibility.PRIVATE);
|
||||
body.instructions = target.instructions;
|
||||
body.instructions.resetLabels();
|
||||
body.tryCatchBlocks = target.tryCatchBlocks;
|
||||
body.localVariables = target.localVariables;
|
||||
body.visibleLocalVariableAnnotations = target.visibleLocalVariableAnnotations;
|
||||
body.invisibleLocalVariableAnnotations = target.invisibleLocalVariableAnnotations;
|
||||
body.maxStack = target.maxStack;
|
||||
body.maxLocals = target.maxLocals;
|
||||
|
||||
target.instructions = new InsnList();
|
||||
target.tryCatchBlocks = new ArrayList<>();
|
||||
target.visibleLocalVariableAnnotations = null;
|
||||
target.invisibleLocalVariableAnnotations = null;
|
||||
target.maxLocals = target.maxStack = Bytecode.getFirstNonArgLocalIndex(target);
|
||||
classNode.methods.add(body);
|
||||
return body;
|
||||
}
|
||||
|
||||
private static String unique(ClassNode classNode, String base) {
|
||||
String name = base;
|
||||
for (int i = 1; ; name = base + "$" + i++) {
|
||||
final String candidate = name;
|
||||
if (classNode.methods.stream().noneMatch(method -> method.name.equals(candidate))) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasLocals(CallSiteSpec spec) {
|
||||
if (spec.entryLocals().length > 0) {
|
||||
return true;
|
||||
}
|
||||
for (int[] locals : spec.locals()) {
|
||||
if (locals.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Frame<BasicValue> frameAt(MethodNode owner, AbstractInsnNode anchor, CallSiteSpec spec) {
|
||||
final int index = owner.instructions.indexOf(anchor);
|
||||
for (owner.maxStack = Math.max(owner.maxStack, spec.target().maxStack) + 16; ; owner.maxStack <<= 1) {
|
||||
try {
|
||||
return new Analyzer<>(new BasicInterpreter()).analyze(owner.name, owner)[index];
|
||||
} catch (AnalyzerException e) {
|
||||
if (!(e.getCause() instanceof IndexOutOfBoundsException) || owner.maxStack > 0xFFFF) {
|
||||
throw cannotFollow(spec, "cannot analyze " + owner.name + owner.desc, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Type[] below(CallSiteSpec spec, Frame<BasicValue> frame, int args) {
|
||||
final int count = frame == null ? 0 : frame.getStackSize() - args;
|
||||
if (count <= 0) {
|
||||
return NOTHING;
|
||||
}
|
||||
final Type[] below = new Type[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
final BasicValue value = frame.getStack(i);
|
||||
final Type type = value.getType();
|
||||
if (type == null || type.getSort() == Type.VOID) {
|
||||
throw cannotFollow(spec, "stack value " + i + " below the call is " + value + ", which cannot be restored", null);
|
||||
}
|
||||
below[i] = value.isReference() ? Type.getObjectType(SurroundSpec.OBJECT) : type;
|
||||
}
|
||||
return below;
|
||||
}
|
||||
|
||||
private static int[] localSlots(CallSiteSpec spec, MethodNode owner, AbstractInsnNode anchor, Frame<BasicValue> frame, int shift, int[] resolved, Type[] params, int first) {
|
||||
if (resolved.length == 0 || owner == spec.target()) {
|
||||
return resolved;
|
||||
}
|
||||
final int firstNonArg = Bytecode.getFirstNonArgLocalIndex(spec.target());
|
||||
final int[] slots = new int[resolved.length];
|
||||
for (int i = 0; i < resolved.length; i++) {
|
||||
final int slot = resolved[i] < firstNonArg ? resolved[i] : resolved[i] + shift;
|
||||
checkLocal(spec, owner, anchor, frame, slot, params[first + i], resolved[i]);
|
||||
slots[i] = slot;
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
private static void checkLocal(CallSiteSpec spec, MethodNode owner, AbstractInsnNode anchor, Frame<BasicValue> frame, int slot, Type type, int resolved) {
|
||||
String found = null;
|
||||
if (owner.localVariables != null) {
|
||||
final int at = owner.instructions.indexOf(anchor);
|
||||
for (LocalVariableNode local : owner.localVariables) {
|
||||
if (local.index == slot && owner.instructions.indexOf(local.start) <= at && at < owner.instructions.indexOf(local.end) && !type.getDescriptor().equals(local.desc)) {
|
||||
found = Type.getType(local.desc).getClassName() + " '" + local.name + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found == null && frame != null) {
|
||||
final BasicValue value = slot < frame.getLocals() ? frame.getLocal(slot) : null;
|
||||
final Type actual = value == null ? null : value.getType();
|
||||
if (actual == null || actual.getSort() == Type.VOID || actual.getSize() != type.getSize() || SurroundBinding.kindOf(actual) != SurroundBinding.kindOf(type)) {
|
||||
found = value == null ? "nothing" : String.valueOf(value);
|
||||
}
|
||||
}
|
||||
if (found != null) {
|
||||
throw cannotFollow(spec, "@Surround.Local " + type.getClassName() + " resolved to local " + resolved + " at injection, but after the body moved to " + owner.name + owner.desc + " local " + slot + " holds " + found, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodNode owner(ClassNode classNode, AbstractInsnNode insn) {
|
||||
AbstractInsnNode first = insn;
|
||||
while (first.getPrevious() != null) {
|
||||
first = first.getPrevious();
|
||||
}
|
||||
final List<MethodNode> methods = classNode.methods;
|
||||
for (int i = 0, n = methods.size(); i < n; i++) {
|
||||
if (methods.get(i).instructions.getFirst() == first) {
|
||||
return methods.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void checkOperands(CallSiteSpec spec, MethodInsnNode call, Type[] args, int offset) {
|
||||
final Type[] operands = spec.operands();
|
||||
if (args.length - offset < operands.length) {
|
||||
throw cannotFollow(spec, "the call was replaced by " + Bytecode.describeNode(call, false) + ", which takes fewer arguments than the surrounded call", null);
|
||||
}
|
||||
for (int i = 0; i < operands.length; i++) {
|
||||
if (SurroundBinding.kindOf(args[offset + i]) != SurroundBinding.kindOf(operands[i])) {
|
||||
throw cannotFollow(spec, "the call was replaced by " + Bytecode.describeNode(call, false) + ", whose argument " + (offset + i) + " is " + args[offset + i].getClassName() + " where the surrounded call passed " + operands[i].getClassName(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IllegalStateException cannotFollow(CallSiteSpec spec, String detail, Throwable cause) {
|
||||
return new IllegalStateException(spec.description() + " cannot be applied, another transformation left the site in a shape it cannot follow: " + detail, cause);
|
||||
}
|
||||
|
||||
private static boolean prefixed(CallSiteSpec spec) {
|
||||
for (Handler exit : spec.exits().handlers) {
|
||||
if (exit.localStart > exit.leading) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int usedLocals(MethodNode owner) {
|
||||
int max = Bytecode.getFirstNonArgLocalIndex(owner);
|
||||
for (AbstractInsnNode insn = owner.instructions.getFirst(); insn != null; insn = insn.getNext()) {
|
||||
if (insn instanceof VarInsnNode varInsn) {
|
||||
final int opcode = insn.getOpcode();
|
||||
final boolean wide = opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE;
|
||||
max = Math.max(max, varInsn.var + (wide ? 2 : 1));
|
||||
} else if (insn instanceof IincInsnNode iinc) {
|
||||
max = Math.max(max, iinc.var + 1);
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
private static void remapLocals(MethodNode inlined, int argBase, int firstNonArg, Type[] entryParams, int entryOperands, int[] spillSlots, int offset, int[] entryLocals, int delta) {
|
||||
final int[] paramSlots = new int[firstNonArg];
|
||||
for (int i = 0, slot = argBase; i < entryParams.length; i++) {
|
||||
paramSlots[slot] = i < entryOperands ? spillSlots[offset + i] : entryLocals[i - entryOperands];
|
||||
slot += entryParams[i].getSize();
|
||||
}
|
||||
for (AbstractInsnNode insn = inlined.instructions.getFirst(); insn != null; insn = insn.getNext()) {
|
||||
if (insn instanceof VarInsnNode var) {
|
||||
var.var = remap(var.var, argBase, firstNonArg, paramSlots, delta);
|
||||
} else if (insn instanceof IincInsnNode iinc) {
|
||||
iinc.var = remap(iinc.var, argBase, firstNonArg, paramSlots, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int remap(int var, int argBase, int firstNonArg, int[] paramSlots, int delta) {
|
||||
if (var < argBase) {
|
||||
return var;
|
||||
}
|
||||
if (var < firstNonArg) {
|
||||
return paramSlots[var];
|
||||
}
|
||||
return var + delta;
|
||||
}
|
||||
|
||||
private static int insertionIndex(MethodNode owner, LabelNode start, LabelNode end) {
|
||||
final List<TryCatchBlockNode> blocks = owner.tryCatchBlocks;
|
||||
final InsnList insns = owner.instructions;
|
||||
final int from = insns.indexOf(start);
|
||||
final int to = insns.indexOf(end);
|
||||
for (int i = 0, n = blocks.size(); i < n; i++) {
|
||||
final TryCatchBlockNode block = blocks.get(i);
|
||||
final int s = insns.indexOf(block.start);
|
||||
final int e = insns.indexOf(block.end);
|
||||
if (s < to && e > from && (s < from || e > to)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return blocks.size();
|
||||
}
|
||||
|
||||
private static void loadBelow(InsnList insns, Type[] below, int[] belowSlots) {
|
||||
for (int i = 0; i < below.length; i++) {
|
||||
insns.add(new VarInsnNode(below[i].getOpcode(Opcodes.ILOAD), belowSlots[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private record Emitter(ClassNode classNode, Exits exits, Type[] operands, int[] operandSlots, int[][] locals, int delta) {
|
||||
|
||||
private void invoke(InsnList insns, Handler exit, Type leading, int leadingSlot) {
|
||||
final int[] locals = this.locals[exit.index];
|
||||
if (!exit.isStatic) {
|
||||
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
||||
}
|
||||
if (leading != null) {
|
||||
insns.add(new VarInsnNode(leading.getOpcode(Opcodes.ILOAD), leadingSlot));
|
||||
}
|
||||
for (int i = 0, n = exit.localStart - exit.leading; i < n; i++) {
|
||||
insns.add(new VarInsnNode(this.operands[i].getOpcode(Opcodes.ILOAD), this.operandSlots[i]));
|
||||
}
|
||||
for (int i = exit.localStart; i < exit.carryStart; i++) {
|
||||
insns.add(new VarInsnNode(exit.params[i].getOpcode(Opcodes.ILOAD), locals[i - exit.localStart]));
|
||||
}
|
||||
for (Load load : exit.loads) {
|
||||
final Type type = load.type();
|
||||
insns.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), load.slot() + this.delta));
|
||||
if (type.getSort() >= Type.ARRAY && !SurroundSpec.OBJECT.equals(type.getInternalName())) {
|
||||
insns.add(new TypeInsnNode(Opcodes.CHECKCAST, type.getInternalName()));
|
||||
}
|
||||
}
|
||||
// MixinExtras 0.5.0 internal (pinned by UniMixins 0.3.1); recheck on bump
|
||||
insns.add(ASMUtils.getInvokeInstruction(this.classNode, exit.method));
|
||||
}
|
||||
|
||||
void callFinally(InsnList insns) {
|
||||
if (this.exits.finallyHandler != null) {
|
||||
this.invoke(insns, this.exits.finallyHandler, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void callSkipped(InsnList insns) {
|
||||
if (this.exits.skipped != null) {
|
||||
this.invoke(insns, this.exits.skipped, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void callReturn(InsnList insns, Type result, int resSlot) {
|
||||
this.invoke(insns, this.exits.returnHandler, result.getSize() > 0 ? result : null, resSlot);
|
||||
}
|
||||
|
||||
void emitHandlers(InsnList tail, List<TryCatchBlockNode> blocks, LabelNode start, LabelNode end, LabelNode retStart, LabelNode retEnd, int excSlot, Type[] below, int[] belowSlots, LabelNode handledExit) {
|
||||
final Handler[] handlers = this.exits.handlers;
|
||||
final boolean hasFinally = this.exits.finallyHandler != null;
|
||||
final LabelNode catchAll = hasFinally ? new LabelNode() : null;
|
||||
final List<TryCatchBlockNode> catchAlls = hasFinally ? new ArrayList<>() : null;
|
||||
for (int k = 0; k < this.exits.catches; k++) {
|
||||
final Handler exit = handlers[k];
|
||||
final LabelNode handler = new LabelNode();
|
||||
final LabelNode exitStart = new LabelNode();
|
||||
final LabelNode exitEnd = new LabelNode();
|
||||
|
||||
tail.add(handler);
|
||||
tail.add(new VarInsnNode(Opcodes.ASTORE, excSlot));
|
||||
if (exit.handles) {
|
||||
loadBelow(tail, below, belowSlots);
|
||||
}
|
||||
tail.add(exitStart);
|
||||
this.invoke(tail, exit, exit.caught(), excSlot);
|
||||
tail.add(exitEnd);
|
||||
|
||||
this.callFinally(tail);
|
||||
if (exit.handles) {
|
||||
tail.add(new JumpInsnNode(Opcodes.GOTO, handledExit));
|
||||
} else {
|
||||
tail.add(new VarInsnNode(Opcodes.ALOAD, excSlot));
|
||||
tail.add(new InsnNode(Opcodes.ATHROW));
|
||||
}
|
||||
|
||||
blocks.add(new TryCatchBlockNode(start, end, handler, exit.caught().getInternalName()));
|
||||
if (hasFinally) {
|
||||
catchAlls.add(new TryCatchBlockNode(exitStart, exitEnd, catchAll, null));
|
||||
}
|
||||
}
|
||||
if (hasFinally) {
|
||||
tail.add(catchAll);
|
||||
tail.add(new VarInsnNode(Opcodes.ASTORE, excSlot));
|
||||
this.callFinally(tail);
|
||||
tail.add(new VarInsnNode(Opcodes.ALOAD, excSlot));
|
||||
tail.add(new InsnNode(Opcodes.ATHROW));
|
||||
|
||||
if (retStart != null) {
|
||||
catchAlls.add(new TryCatchBlockNode(retStart, retEnd, catchAll, null));
|
||||
}
|
||||
catchAlls.add(new TryCatchBlockNode(start, end, catchAll, null));
|
||||
blocks.addAll(catchAlls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.code.Injector;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionInfo;
|
||||
import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException;
|
||||
import org.spongepowered.asm.mixin.transformer.MixinTargetContext;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@InjectionInfo.AnnotationType(Surround.class)
|
||||
@InjectionInfo.HandlerPrefix("surround")
|
||||
public final class SurroundInjectionInfo extends InjectionInfo {
|
||||
|
||||
boolean callSite;
|
||||
|
||||
public SurroundInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) {
|
||||
super(mixin, method, annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readInjectionPoints() {
|
||||
final List<AnnotationNode> ats = Annotations.<AnnotationNode>getValue(this.annotation, this.atKey, false);
|
||||
this.callSite = ats != null && !ats.isEmpty();
|
||||
if (this.callSite) {
|
||||
super.readInjectionPoints();
|
||||
return;
|
||||
}
|
||||
final List<AnnotationNode> slices = Annotations.<AnnotationNode>getValue(this.annotation, "slice", false);
|
||||
if (slices != null && !slices.isEmpty()) {
|
||||
throw this.reject("declares @Surround.slice, which only applies when @Surround.at selects a call");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseInjectionPoints(List<AnnotationNode> ats) {
|
||||
if (!this.callSite) {
|
||||
this.injectionPoints.add(new SurroundInjectionPoint());
|
||||
return;
|
||||
}
|
||||
for (int i = 0, n = ats.size(); i < n; i++) {
|
||||
final AnnotationNode at = ats.get(i);
|
||||
final String code = Annotations.<String>getValue(at, "value");
|
||||
if (!selectsCall(code)) {
|
||||
throw this.reject("has @At(\"" + code + "\"), which does not select a method call");
|
||||
}
|
||||
if (Annotations.<At.Shift>getValue(at, "shift", At.Shift.class, At.Shift.NONE) != At.Shift.NONE || Annotations.<Integer>getValue(at, "by", Integer.valueOf(0)).intValue() != 0) {
|
||||
throw this.reject("has @At(\"" + code + "\") with a non-default shift or by, which moves the match off the call");
|
||||
}
|
||||
}
|
||||
super.parseInjectionPoints(ats);
|
||||
}
|
||||
|
||||
private static boolean selectsCall(String atCode) {
|
||||
return switch (atCode) {
|
||||
case "HEAD", "RETURN", "TAIL", "CTOR_HEAD", "FIELD", "NEW", "CONSTANT", "JUMP", "INVOKE_ASSIGN", "LOAD", "STORE" -> false;
|
||||
default -> true;
|
||||
};
|
||||
}
|
||||
|
||||
private InvalidInjectionException reject(String message) {
|
||||
return new InvalidInjectionException(this, "@Surround " + this.getMethodName() + " " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Injector parseInjector(AnnotationNode injectAnnotation) {
|
||||
return new SurroundInjector(this);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.InsnList;
|
||||
import org.spongepowered.asm.mixin.injection.InjectionPoint;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
final class SurroundInjectionPoint extends InjectionPoint {
|
||||
|
||||
@Override
|
||||
public boolean checkPriority(int targetPriority, int ownerPriority) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean find(String desc, InsnList insns, Collection<AbstractInsnNode> nodes) {
|
||||
return nodes.add(insns.getFirst());
|
||||
}
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Exits;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Handler;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Role;
|
||||
import com.llamalad7.mixinextras.utils.InjectorUtils;
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableAnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.injection.InjectionPoint;
|
||||
import org.spongepowered.asm.mixin.injection.code.Injector;
|
||||
import org.spongepowered.asm.mixin.injection.modify.InvalidImplicitDiscriminatorException;
|
||||
import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator;
|
||||
import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator.Context;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode;
|
||||
import org.spongepowered.asm.mixin.injection.struct.Target;
|
||||
import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException;
|
||||
import org.spongepowered.asm.mixin.transformer.ClassInfo;
|
||||
import org.spongepowered.asm.mixin.transformer.ClassInfo.Traversal;
|
||||
import org.spongepowered.asm.mixin.transformer.MixinTargetContext;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
import org.spongepowered.asm.util.Bytecode;
|
||||
import org.spongepowered.asm.util.asm.MethodNodeEx;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.exitRef;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.paramRef;
|
||||
|
||||
final class SurroundInjector extends Injector {
|
||||
|
||||
private final String id;
|
||||
private final boolean callSite;
|
||||
private final MixinTargetContext mixin;
|
||||
private Handler entry;
|
||||
private Exits exits;
|
||||
|
||||
SurroundInjector(SurroundInjectionInfo info) {
|
||||
super(info, "@Surround");
|
||||
this.id = info.getAnnotation().getValue("id", "");
|
||||
this.callSite = info.callSite;
|
||||
this.mixin = (MixinTargetContext) info.getMixin();
|
||||
SurroundApplicatorExtension.instance().declare(this.mixin.getTarget(), this.mixin.getClassName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(Target target, InjectionNode node) {
|
||||
this.checkTargetModifiers(target, true);
|
||||
try {
|
||||
if (this.callSite) {
|
||||
this.surroundCall(target, node);
|
||||
} else {
|
||||
final Type result = Type.getReturnType(target.method.desc);
|
||||
this.prepare(target.arguments, result, target.toString());
|
||||
this.offer(target, null, target.arguments, result, target.toString());
|
||||
}
|
||||
} catch (SurroundRejection e) {
|
||||
throw error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void surroundCall(Target target, InjectionNode node) {
|
||||
final AbstractInsnNode insn = node.getOriginalTarget();
|
||||
if (!(insn instanceof MethodInsnNode call)) {
|
||||
throw error("at must select a method call, not " + Bytecode.describeNode(insn, false) + " in " + target);
|
||||
}
|
||||
final String site = "call " + call.owner + "." + call.name + call.desc + " in " + target;
|
||||
if ("<init>".equals(call.name)) {
|
||||
throw error("at must select a method call, not the constructor " + site);
|
||||
}
|
||||
final Type[] operands = SurroundCallSite.operands(call, this.classNode.name);
|
||||
final Type result = Type.getReturnType(call.desc);
|
||||
this.prepare(operands, result, site);
|
||||
final boolean insideNew = isInsideNew(call);
|
||||
if (insideNew && this.methodNode.tryCatchBlocks != null && !this.methodNode.tryCatchBlocks.isEmpty()) {
|
||||
throw error("must not catch exceptions when it surrounds a call inside a constructor call's arguments (between NEW and <init>); move the try into a method it calls");
|
||||
}
|
||||
if (insideNew && this.exits.handles) {
|
||||
throw error("has a @Surround.Catch" + this.forId() + " that declares handle = true, but the call is inside a constructor call's arguments (between NEW and <init>), where the values below it cannot be restored");
|
||||
}
|
||||
this.offer(target, node, operands, result, site);
|
||||
}
|
||||
|
||||
private void offer(Target target, InjectionNode node, Type[] operands, Type result, String site) {
|
||||
final Handler[] handlers = this.exits.handlers;
|
||||
final int[][] locals = new int[handlers.length][];
|
||||
for (Handler exit : handlers) {
|
||||
locals[exit.index] = this.resolveLocals(target, node, exit);
|
||||
}
|
||||
final int[] entryLocals = this.resolveLocals(target, node, this.entry);
|
||||
this.info.addCallbackInvocation(this.methodNode);
|
||||
final String description = "@Surround " + this.mixin.getClassName() + "::" + this.info.getMethodName() + " surrounding " + site;
|
||||
SurroundApplicatorExtension.instance().offer(this.mixin.getTarget(), new CallSiteSpec(target.method, node, this.methodNode, this.methodNode.desc, operands, result, this.exits, description, entryLocals, locals));
|
||||
}
|
||||
|
||||
private static boolean isInsideNew(MethodInsnNode call) {
|
||||
int constructed = 0;
|
||||
for (AbstractInsnNode insn = call.getPrevious(); insn != null; insn = insn.getPrevious()) {
|
||||
if (insn.getOpcode() == Opcodes.INVOKESPECIAL && "<init>".equals(((MethodInsnNode) insn).name)) {
|
||||
constructed++;
|
||||
} else if (insn.getOpcode() == Opcodes.NEW) {
|
||||
if (constructed == 0) {
|
||||
return true;
|
||||
}
|
||||
constructed--;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int[] resolveLocals(Target target, InjectionNode node, Handler handler) {
|
||||
final int from = handler.localStart;
|
||||
final int[] slots = new int[handler.carryStart - from];
|
||||
for (int i = from; i < handler.carryStart; i++) {
|
||||
final Type type = handler.params[i];
|
||||
final LocalVariableDiscriminator discriminator = LocalVariableDiscriminator.parse(SurroundBinding.local(handler.method, i));
|
||||
// MixinExtras 0.5.0 internal (pinned by UniMixins 0.3.1); recheck on bump
|
||||
final Context context = InjectorUtils.getOrCreateLocalContext(target, node, this.info, type, discriminator.isArgsOnly());
|
||||
String detail;
|
||||
try {
|
||||
slots[i - from] = discriminator.findLocal(context);
|
||||
detail = slots[i - from] < 0 ? "no " + discriminator.toString(context) : null;
|
||||
} catch (InvalidImplicitDiscriminatorException e) {
|
||||
detail = e.getMessage();
|
||||
}
|
||||
if (detail != null) {
|
||||
throw error(handler.ref() + " " + paramRef(handler.method, i) + " is @Surround.Local " + type.getClassName() + ", which matches no single local at the call: " + detail);
|
||||
}
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
private void prepare(Type[] expected, Type result, String against) {
|
||||
if (this.exits == null) {
|
||||
this.analyze(result);
|
||||
}
|
||||
this.checkArgPrefix(this.entry, expected, against);
|
||||
for (Handler exit : this.exits.handlers) {
|
||||
if (exit.role == Role.SKIPPED || exit.role == Role.RETURN || exit.handles) {
|
||||
final Type declared = Type.getReturnType(exit.method.desc);
|
||||
if (!declared.equals(result)) {
|
||||
throw error(exit.ref() + " must return " + result.getClassName() + " to match what it surrounds, not " + declared.getClassName());
|
||||
}
|
||||
}
|
||||
if (exit.role == Role.RETURN && exit.leading == 1 && (exit.params.length == 0 || !exit.params[0].equals(result))) {
|
||||
throw error(exit.ref() + " must declare the " + result.getClassName() + " result as its first parameter");
|
||||
}
|
||||
this.checkArgPrefix(exit, expected, against);
|
||||
}
|
||||
if (this.exits.skipped == null && this.exits.skipSlot >= 0 && result.getSize() > 0) {
|
||||
throw error("can skip a " + result.getClassName() + " result but has no @Surround.Skipped" + this.forId() + " to supply it");
|
||||
}
|
||||
}
|
||||
|
||||
private void analyze(Type result) {
|
||||
if (!this.returnType.equals(Type.VOID_TYPE)) {
|
||||
throw error("must return void; carry state out with @Surround.Carry locals instead");
|
||||
}
|
||||
if (Bytecode.hasFlag(this.methodNode, Opcodes.ACC_ABSTRACT)) {
|
||||
throw error("must not be abstract");
|
||||
}
|
||||
final Handler entry = new Handler(this.methodNode, Role.ENTRY, 0);
|
||||
if (this.hasStrippedSugar()) {
|
||||
throw error("one of its parameters is annotated @Local, @Share or @Cancellable" + SurroundBinding.SUGAR_HINT);
|
||||
}
|
||||
this.checkNoLocal(entry);
|
||||
final AbstractInsnNode soleReturn = SurroundBinding.soleReturn(this.methodNode);
|
||||
|
||||
final Set<String> ids = new LinkedHashSet<>();
|
||||
for (MethodNode method : this.mixin.getMixin().getClassNode(0).methods) {
|
||||
final AnnotationNode surround = Annotations.get(method.visibleAnnotations, SurroundSpec.SURROUND);
|
||||
if (surround == null) {
|
||||
continue;
|
||||
}
|
||||
final String declared = Annotations.<String>getValue(surround, "id", "");
|
||||
if (!ids.add(declared) && declared.equals(this.id)) {
|
||||
throw error("duplicate @Surround " + describeId(this.id) + "; ids must be unique per mixin");
|
||||
}
|
||||
}
|
||||
final List<Handler> handlers = new ArrayList<>();
|
||||
for (MethodNode method : this.mixin.getClassNode().methods) {
|
||||
Role role = null;
|
||||
AnnotationNode annotation = null;
|
||||
for (Role candidate : Role.EXITS) {
|
||||
final AnnotationNode found = Annotations.get(method.visibleAnnotations, candidate.annotation);
|
||||
if (found == null) {
|
||||
continue;
|
||||
}
|
||||
if (role != null) {
|
||||
throw error(exitRef(method) + " can be only one of @Surround.Catch, @Surround.Finally, @Surround.Skipped and @Surround.Return");
|
||||
}
|
||||
role = candidate;
|
||||
annotation = found;
|
||||
}
|
||||
if (role == null) {
|
||||
continue;
|
||||
}
|
||||
final String exitId = Annotations.<String>getValue(annotation, "value", "");
|
||||
if (!ids.contains(exitId)) {
|
||||
throw error(exitRef(method) + " " + noSurroundForId(exitId, ids));
|
||||
}
|
||||
if (this.id.equals(exitId)) {
|
||||
handlers.add(this.exitHandler(this.own(method), role, role == Role.CATCH || role == Role.RETURN && result.getSize() > 0 ? 1 : 0));
|
||||
}
|
||||
}
|
||||
handlers.sort(Comparator.comparing(handler -> handler.role));
|
||||
boolean skipped = false;
|
||||
for (int i = 0, n = handlers.size(); i < n; i++) {
|
||||
final Role role = handlers.get(i).role;
|
||||
skipped |= role == Role.SKIPPED;
|
||||
if (i > 0 && role != Role.CATCH && role == handlers.get(i - 1).role) {
|
||||
throw error("declares more than one @Surround." + role.name().charAt(0) + role.name().substring(1).toLowerCase(Locale.ROOT) + this.forId() + "; there can only be one");
|
||||
}
|
||||
}
|
||||
final Handler[] sorted = handlers.toArray(new Handler[0]);
|
||||
this.sortMostSpecificFirst(sorted);
|
||||
final List<LocalVariableAnnotationNode> annotations = this.methodNode.visibleLocalVariableAnnotations;
|
||||
final int[] kinds = annotations == null || annotations.isEmpty() ? null : SurroundBinding.kindsAtEnd(this.methodNode, soleReturn);
|
||||
final int skipSlot = SurroundBinding.skipSlot(this.methodNode, kinds);
|
||||
if (skipped && skipSlot < 0) {
|
||||
throw error("has a @Surround.Skipped" + this.forId() + " but declares no @Surround.Skip local, so nothing is ever skipped");
|
||||
}
|
||||
final Type[] carried = SurroundBinding.bind(this.methodNode, kinds, sorted);
|
||||
this.entry = entry;
|
||||
this.exits = new Exits(sorted, skipSlot, carried);
|
||||
}
|
||||
|
||||
// MixinExtras strips sugar params before parse; the annotation table can then be null.
|
||||
private boolean hasStrippedSugar() {
|
||||
final List<AnnotationNode>[] invisible = this.methodNode.invisibleParameterAnnotations;
|
||||
if (invisible != null) {
|
||||
return invisible.length != this.methodArgs.length;
|
||||
}
|
||||
final ClassInfo mixin = this.mixin.getClassInfo();
|
||||
final String desc = this.methodNode.desc;
|
||||
return mixin.findMethod(MethodNodeEx.getName(this.methodNode), desc, ClassInfo.INCLUDE_ALL) == null && mixin.findMethod(this.methodNode.name, desc, ClassInfo.INCLUDE_ALL) == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sanityCheck(Target target, List<InjectionPoint> injectionPoints) {
|
||||
super.sanityCheck(target, injectionPoints);
|
||||
if (this.isInterface) {
|
||||
throw error("cannot target an interface method " + target);
|
||||
}
|
||||
if (target.method.name.endsWith("init>")) {
|
||||
throw error("cannot target the initializer " + target);
|
||||
}
|
||||
if (Bytecode.hasFlag(target.method, Opcodes.ACC_ABSTRACT) || Bytecode.hasFlag(target.method, Opcodes.ACC_NATIVE)) {
|
||||
throw error("cannot target the abstract or native method " + target);
|
||||
}
|
||||
}
|
||||
|
||||
private Handler exitHandler(MethodNode method, Role role, int leading) {
|
||||
final Handler exit = new Handler(method, role, leading);
|
||||
if (exit.handles && !this.callSite) {
|
||||
throw error(exit.ref() + " declares handle = true, which only applies when @Surround.at selects a call");
|
||||
}
|
||||
if (Bytecode.hasFlag(method, Opcodes.ACC_ABSTRACT)) {
|
||||
throw error(exit.ref() + " must not be abstract");
|
||||
}
|
||||
if ((role == Role.FINALLY || role == Role.CATCH && !exit.handles) && !Type.getReturnType(method.desc).equals(Type.VOID_TYPE)) {
|
||||
throw error(exit.ref() + " must return void; an exit handler cannot suppress or replace anything");
|
||||
}
|
||||
if (Bytecode.isStatic(method) != this.isStatic) {
|
||||
throw error(exit.ref() + " must be " + (this.isStatic ? "static" : "non-static") + " to match the entry handler" + this.forId());
|
||||
}
|
||||
if (role == Role.CATCH) {
|
||||
if (exit.params.length == 0) {
|
||||
throw error(exit.ref() + " must declare the Throwable it catches as its first parameter");
|
||||
}
|
||||
final Type caught = exit.caught();
|
||||
if (caught.getSort() != Type.OBJECT || !SurroundSpec.THROWABLE.equals(caught.getInternalName()) && !extendsClass(caught, SurroundSpec.THROWABLE)) {
|
||||
throw error(exit.ref() + " " + paramRef(method, 0) + " catches " + caught.getClassName() + ", which is not a java.lang.Throwable subtype");
|
||||
}
|
||||
}
|
||||
this.checkNoLocal(exit);
|
||||
return exit;
|
||||
}
|
||||
|
||||
private void checkNoLocal(Handler handler) {
|
||||
if (!this.callSite && handler.localStart < handler.carryStart) {
|
||||
throw error(handler.ref() + " " + paramRef(handler.method, handler.localStart) + " is @Surround.Local, which only applies when @Surround.at selects a call");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkArgPrefix(Handler handler, Type[] expected, String against) {
|
||||
final Type[] params = handler.params;
|
||||
if (handler.localStart - handler.leading > expected.length) {
|
||||
throw error(handler.ref() + " has more parameters than " + against);
|
||||
}
|
||||
for (int i = handler.leading, arg = 0; i < handler.localStart; i++, arg++) {
|
||||
if (params[i].equals(expected[arg]) || this.callSite && isAssignable(params[i], expected[arg])) {
|
||||
continue;
|
||||
}
|
||||
throw error(handler.ref() + " " + paramRef(handler.method, i) + " is " + params[i].getClassName() + ", which does not match the " + expected[arg].getClassName() + " operand of " + against);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAssignable(Type param, Type operand) {
|
||||
if (param.getSort() != Type.OBJECT || operand.getSort() != Type.OBJECT && operand.getSort() != Type.ARRAY) {
|
||||
return false;
|
||||
}
|
||||
if (SurroundSpec.OBJECT.equals(param.getInternalName())) {
|
||||
return true;
|
||||
}
|
||||
if (operand.getSort() != Type.OBJECT) {
|
||||
return false;
|
||||
}
|
||||
final ClassInfo info = ClassInfo.forName(operand.getInternalName());
|
||||
return info != null && info.findSuperClass(param.getInternalName(), Traversal.ALL, true) != null;
|
||||
}
|
||||
|
||||
private void sortMostSpecificFirst(Handler[] exits) {
|
||||
for (int k = 1; k < exits.length && exits[k].role == Role.CATCH; k++) {
|
||||
final Handler exit = exits[k];
|
||||
final Type caught = exit.caught();
|
||||
int at = k;
|
||||
for (int i = 0; i < k; i++) {
|
||||
final Type other = exits[i].caught();
|
||||
if (caught.equals(other)) {
|
||||
throw error(exit.ref() + " is a second @Surround.Catch for " + caught.getClassName() + this.forId() + "; each type may be caught once");
|
||||
}
|
||||
if (at == k && extendsClass(caught, other.getInternalName())) {
|
||||
at = i;
|
||||
}
|
||||
}
|
||||
System.arraycopy(exits, at, exits, at + 1, k - at);
|
||||
exits[at] = exit;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean extendsClass(Type type, String superName) {
|
||||
final ClassInfo info = ClassInfo.forName(type.getInternalName());
|
||||
return info != null && info.hasSuperClass(superName);
|
||||
}
|
||||
|
||||
private MethodNode own(MethodNode exit) {
|
||||
final MethodNode method = Bytecode.findMethod(this.classNode, exit.name, exit.desc);
|
||||
if (method == null) {
|
||||
throw new IllegalStateException(exit.name + exit.desc + " is missing from " + this.classNode.name);
|
||||
}
|
||||
if (!this.mixin.getClassName().equals(SurroundSpec.mergedBy(method))) {
|
||||
throw error(exitRef(exit) + " collides with " + exit.name + exit.desc + " in " + this.classNode.name + ", which is " + SurroundSpec.describeOwner(method));
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
private String forId() {
|
||||
return this.id.isEmpty() ? "" : " for id '" + this.id + "'";
|
||||
}
|
||||
|
||||
private static String describeId(String id) {
|
||||
return id.isEmpty() ? "the default id" : "id '" + id + "'";
|
||||
}
|
||||
|
||||
static String noSurroundForId(String id, Collection<String> declared) {
|
||||
final StringJoiner ids = new StringJoiner(", ", "; declared ids are ", "").setEmptyValue("; this mixin has no @Surround at all");
|
||||
for (String other : declared) {
|
||||
ids.add(describeId(other));
|
||||
}
|
||||
return "refers to " + describeId(id) + " but no @Surround in this mixin declares it" + ids;
|
||||
}
|
||||
|
||||
private InvalidInjectionException error(String message) {
|
||||
return new InvalidInjectionException(this.info, "@Surround " + this.info.getMethodName() + " " + message);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
final class SurroundRejection extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
SurroundRejection(String message) {
|
||||
super(message, null, false, false);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.transformer.meta.MixinMerged;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
|
||||
interface SurroundSpec {
|
||||
|
||||
String SURROUND = Type.getDescriptor(Surround.class);
|
||||
String CATCH = Type.getDescriptor(Surround.Catch.class);
|
||||
String FINALLY = Type.getDescriptor(Surround.Finally.class);
|
||||
String CARRY = Type.getDescriptor(Surround.Carry.class);
|
||||
String SKIP = Type.getDescriptor(Surround.Skip.class);
|
||||
String SKIPPED = Type.getDescriptor(Surround.Skipped.class);
|
||||
String RETURN = Type.getDescriptor(Surround.Return.class);
|
||||
String LOCAL = Type.getDescriptor(Surround.Local.class);
|
||||
String MIXIN_MERGED = Type.getDescriptor(MixinMerged.class);
|
||||
String OBJECT = Type.getInternalName(Object.class);
|
||||
String THROWABLE = Type.getInternalName(Throwable.class);
|
||||
String THROWABLE_DESC = Type.getDescriptor(Throwable.class);
|
||||
|
||||
static String mergedBy(MethodNode method) {
|
||||
final AnnotationNode merged = Annotations.get(method.visibleAnnotations, MIXIN_MERGED);
|
||||
return merged == null ? null : Annotations.<String>getValue(merged, "mixin");
|
||||
}
|
||||
|
||||
static String describeOwner(MethodNode method) {
|
||||
final String owner = mergedBy(method);
|
||||
return owner == null ? "not merged from a mixin" : "merged from " + owner;
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Entity;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.ReportedException;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.lib.tree.TryCatchBlockNode;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.CALL;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.call;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.method;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.transformedNode;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CallSiteTest {
|
||||
|
||||
@BeforeEach
|
||||
void resetTrace() {
|
||||
Trace.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aRedirectedCallInsideJavacTryCatchesRethrowsInsideTheEnclosingTry() throws Exception {
|
||||
assertEquals(true, call(CALL, "render", new Entity("pig"), 1.0d, 2.0d, 3.0d, 0.5f, 0.25f, true));
|
||||
final Entity creeper = new Entity("creeper");
|
||||
creeper.explode = true;
|
||||
final InvocationTargetException thrown = assertThrows(InvocationTargetException.class, () -> call(CALL, "render", creeper, 1.0d, 2.0d, 3.0d, 0.5f, 0.25f, false));
|
||||
|
||||
final ReportedException outer = (ReportedException) thrown.getCause();
|
||||
assertEquals("Rendering entity (outer)", outer.getMessage());
|
||||
assertEquals("Rendering entity in world", outer.getCause().getMessage());
|
||||
assertSame(RuntimeException.class, outer.getCause().getCause().getClass());
|
||||
assertEquals("gpu", outer.getCause().getCause().getMessage());
|
||||
Trace.assertEvents("enterDoRender pig 1", "redirect pig", "doRender pig 1.0 0.25", "exitDoRender pig 1", "shadow pig", "boundingBox pig", "enterDoRender creeper 2", "redirect creeper", "doRender creeper 1.0 0.25", "caughtDoRender creeper gpu 2", "exitDoRender creeper 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theSurroundedCallMatchesTheGoldens() {
|
||||
MixinTestBootstrap.assertGolden(CALL, "valueSkip", "call-skip");
|
||||
MixinTestBootstrap.assertGolden(CALL, "valueBelow", "call-handle");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedSurroundsShareOneCallAndOrderTheirTryRangesInnerFirst() {
|
||||
final MethodNode target = method(transformedNode(CALL), "innerSkipOuterReturn");
|
||||
int calls = 0;
|
||||
for (AbstractInsnNode insn : target.instructions.toArray()) {
|
||||
calls += insn instanceof MethodInsnNode m && m.name.equals("twice") ? 1 : 0;
|
||||
}
|
||||
assertEquals(1, calls);
|
||||
final List<TryCatchBlockNode> blocks = target.tryCatchBlocks;
|
||||
boolean nests = false;
|
||||
for (int i = 0; i < blocks.size(); i++) {
|
||||
for (int j = 0; j < blocks.size(); j++) {
|
||||
final boolean encloses = encloses(target, blocks.get(i), blocks.get(j));
|
||||
nests |= encloses;
|
||||
assertTrue(!encloses || i > j, "block " + i + " encloses block " + j);
|
||||
}
|
||||
}
|
||||
assertTrue(nests, "no try block encloses another");
|
||||
}
|
||||
|
||||
private static boolean encloses(MethodNode target, TryCatchBlockNode outer, TryCatchBlockNode inner) {
|
||||
final int outerStart = target.instructions.indexOf(outer.start);
|
||||
final int outerEnd = target.instructions.indexOf(outer.end);
|
||||
final int innerStart = target.instructions.indexOf(inner.start);
|
||||
final int innerEnd = target.instructions.indexOf(inner.end);
|
||||
return outerStart <= innerStart && innerEnd <= outerEnd && (outerStart < innerStart || innerEnd < outerEnd);
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import com.llamalad7.mixinextras.utils.MixinInternals;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.InsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.transformer.ext.Extensions;
|
||||
import org.spongepowered.asm.mixin.transformer.ext.IExtension;
|
||||
import org.spongepowered.asm.mixin.transformer.meta.MixinMerged;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.WRAP;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.of;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.scenario;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CompositionTest {
|
||||
|
||||
private static final Set<String> RUN_BEFORE = Set.of("LateInjectionApplicatorExtension", "SugarPostProcessingExtension", "WrapMethodApplicatorExtension");
|
||||
private static final String CHECK_CLASS = "ExtensionCheckClass";
|
||||
|
||||
@BeforeEach
|
||||
void resetTrace() {
|
||||
Trace.reset();
|
||||
}
|
||||
|
||||
private static Object wrap(String name, Object... args) throws Exception {
|
||||
return MixinTestBootstrap.call(WRAP, name, args);
|
||||
}
|
||||
|
||||
static Stream<Arguments> mixinExtras() {
|
||||
return Stream.of(
|
||||
scenario(WRAP, "the surround is outside @WrapMethod", "wrapped", of(3), of(6), "entry 3", "wrap-before 3", "body 3", "wrap-after 6", "finally"),
|
||||
scenario(WRAP, "the entry sees the operand before @WrapOperation shifts it", "originalOperands", of(5), of(210), "enter 5", "wrap 5", "twice 105", "exit 5"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("mixinExtras")
|
||||
@Tag("mixinextras")
|
||||
void mixinExtras(String method, String target, Object[] inputs, Object[] outputs, String[] events) throws Exception {
|
||||
MixinTestBootstrap.assertScenario(target, method, inputs, outputs, events);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("mixinextras")
|
||||
void aLocalFollowsTheBodyWhenAShareShiftsItsSlot() throws Exception {
|
||||
assertEquals(10, wrap("shifted", 3));
|
||||
Trace.assertEvents("wrap 3", "enterShifted 4 4 L4", "twice 4", "exitShifted 4 L4");
|
||||
MethodNode body = null;
|
||||
for (MethodNode method : MixinTestBootstrap.transformedNode(WRAP).methods) {
|
||||
body = method.name.startsWith("shifted$") ? method : body;
|
||||
}
|
||||
assertTrue(body != null && body.desc.startsWith("(IL"), "the body did not move and gain the share");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("mixinextras")
|
||||
void ourExtensionRunsAfterMixinExtrasAndBeforeCheckClass() throws Exception {
|
||||
MixinTestBootstrap.transformer();
|
||||
final SurroundApplicatorExtension ours = SurroundApplicatorExtension.instance();
|
||||
MixinTestBootstrap.transform(WRAP);
|
||||
final Extensions extensions = MixinInternals.getExtensions();
|
||||
for (List<IExtension> list : List.of(extensions.getExtensions(), extensions.getActiveExtensions())) {
|
||||
final int at = assertSlot(list, ours);
|
||||
for (String name : RUN_BEFORE) {
|
||||
final int index = names(list).indexOf(name);
|
||||
assertTrue(index >= 0 && index < at, names(list).toString());
|
||||
assertTrue(list.get(index).getClass().getName().startsWith("com.llamalad7.mixinextras."));
|
||||
}
|
||||
}
|
||||
assertCheckClassActiveUnderVerify(extensions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUnrenamedSurroundHandlerIsFatal() {
|
||||
final String mixin = "com.example.mixin.MixinLate";
|
||||
final MethodNode method = new MethodNode(Opcodes.ACC_PRIVATE, "surround$enter", "()V", null, null);
|
||||
method.instructions.add(new InsnNode(Opcodes.RETURN));
|
||||
method.visitAnnotation(Type.getDescriptor(Surround.class), true).visitEnd();
|
||||
method.visitAnnotation(Type.getDescriptor(MixinMerged.class), true).visit("mixin", mixin);
|
||||
final ClassNode target = new ClassNode();
|
||||
target.version = Opcodes.V1_8;
|
||||
target.access = Opcodes.ACC_PUBLIC;
|
||||
target.name = "com/example/Target";
|
||||
target.superName = "java/lang/Object";
|
||||
target.methods.add(method);
|
||||
|
||||
final String message = assertThrows(IllegalStateException.class, () -> SurroundApplicatorExtension.checkHandlersRenamed(target, Set.of())).getMessage();
|
||||
assertTrue(message.startsWith("surround$enter()V in com/example/Target (merged from " + mixin + ") carries @Surround but was never parsed as an injector"), message);
|
||||
assertTrue(message.contains("SurroundBootstrap.init() must run before"), message);
|
||||
}
|
||||
|
||||
private static void assertCheckClassActiveUnderVerify(Extensions extensions) {
|
||||
final List<String> active = names(extensions.getActiveExtensions());
|
||||
assertEquals(Boolean.getBoolean("mixin.debug.verify"), active.contains(CHECK_CLASS), active.toString());
|
||||
}
|
||||
|
||||
private static List<String> names(List<IExtension> list) {
|
||||
final List<String> names = new ArrayList<>(list.size());
|
||||
for (IExtension extension : list) {
|
||||
names.add(extension.getClass().getSimpleName());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private static int assertSlot(List<IExtension> list, IExtension ours) {
|
||||
final List<String> names = names(list);
|
||||
assertEquals(list.indexOf(ours), list.lastIndexOf(ours), "registered twice: " + names);
|
||||
final int at = list.indexOf(ours);
|
||||
assertTrue(at >= 0, "missing: " + names);
|
||||
for (String after : names.subList(at + 1, names.size())) {
|
||||
assertFalse(RUN_BEFORE.contains(after), after + " runs after us: " + names);
|
||||
}
|
||||
final int check = names.indexOf(CHECK_CLASS);
|
||||
assertTrue(check < 0 || check > at, CHECK_CLASS + " runs before us: " + names);
|
||||
return at;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import makamys.mixingasm.api.MixinSafeTransformer;
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.commons.LocalVariablesSorter;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@MixinSafeTransformer
|
||||
public final class LocalSortingTransformer implements IClassTransformer {
|
||||
|
||||
public static final Set<String> SEEN = ConcurrentHashMap.newKeySet();
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] bytes) {
|
||||
SEEN.add(transformedName);
|
||||
if (bytes == null || ((bytes[6] & 0xFF) << 8 | bytes[7] & 0xFF) > Opcodes.V1_8) {
|
||||
return bytes;
|
||||
}
|
||||
final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
new ClassReader(bytes).accept(new ClassVisitor(Opcodes.ASM5, writer) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
return new LocalVariablesSorter(Opcodes.ASM5, access, desc, super.visitMethod(access, name, desc, signature, exceptions)) {
|
||||
};
|
||||
}
|
||||
}, ClassReader.EXPAND_FRAMES);
|
||||
return writer.toByteArray();
|
||||
}
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import com.llamalad7.mixinextras.MixinExtrasBootstrap;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import net.minecraft.launchwrapper.LaunchClassLoader;
|
||||
import org.junit.jupiter.api.Named;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.spongepowered.asm.launch.MixinBootstrap;
|
||||
import org.spongepowered.asm.lib.ClassReader;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.lib.util.Textifier;
|
||||
import org.spongepowered.asm.lib.util.TraceClassVisitor;
|
||||
import org.spongepowered.asm.mixin.EnvironmentStateTweaker;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.Mixins;
|
||||
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public final class MixinTestBootstrap {
|
||||
|
||||
private MixinTestBootstrap() {
|
||||
}
|
||||
|
||||
private static final class Holder {
|
||||
|
||||
static final IMixinTransformer TRANSFORMER = boot();
|
||||
}
|
||||
|
||||
public static IMixinTransformer transformer() {
|
||||
return Holder.TRANSFORMER;
|
||||
}
|
||||
|
||||
private static final Map<String, byte[]> TRANSFORMED = new HashMap<>();
|
||||
private static final Map<String, Class<?>> DEFINED = new HashMap<>();
|
||||
|
||||
public static byte[] transformedBytes(String binaryName) {
|
||||
return TRANSFORMED.computeIfAbsent(binaryName, name -> transformer().transformClassBytes(name, name, read(name)));
|
||||
}
|
||||
|
||||
public static Class<?> transform(String binaryName) {
|
||||
return DEFINED.computeIfAbsent(binaryName, name -> define(name, transformedBytes(name)));
|
||||
}
|
||||
|
||||
public static Object call(String target, String name, Object... args) throws Exception {
|
||||
final Class<?> owner = transform(target);
|
||||
for (Method method : owner.getMethods()) {
|
||||
if (method.getName().equals(name) && method.getParameterCount() == args.length) {
|
||||
final boolean isStatic = Modifier.isStatic(method.getModifiers());
|
||||
return method.invoke(isStatic ? null : owner.getDeclaredConstructor().newInstance(), args);
|
||||
}
|
||||
}
|
||||
throw new AssertionError("no " + name + " in " + target);
|
||||
}
|
||||
|
||||
static final String WHOLE = "com.gtnewhorizons.angelica.experimental.surround.integration.WholeTarget";
|
||||
static final String CALL = "com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget";
|
||||
static final String WRAP = "com.gtnewhorizons.angelica.experimental.surround.integration.WrapTarget";
|
||||
|
||||
static ClassNode transformedNode(String target) {
|
||||
return parse(transformedBytes(target));
|
||||
}
|
||||
|
||||
static ClassNode originalNode(String binaryName) {
|
||||
return parse(read(binaryName));
|
||||
}
|
||||
|
||||
private static ClassNode parse(byte[] bytes) {
|
||||
final ClassNode node = new ClassNode();
|
||||
new ClassReader(bytes).accept(node, 0);
|
||||
return node;
|
||||
}
|
||||
|
||||
static MethodNode method(ClassNode node, String name) {
|
||||
for (MethodNode method : node.methods) {
|
||||
if (method.name.equals(name)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("no method " + name + " in " + node.name);
|
||||
}
|
||||
|
||||
static void assertGolden(String target, String method, String golden) {
|
||||
final ClassNode full = transformedNode(target);
|
||||
final ClassNode slim = new ClassNode();
|
||||
slim.visit(full.version, full.access, full.name, null, full.superName, null);
|
||||
slim.methods.add(method(full, method));
|
||||
for (MethodNode candidate : full.methods) {
|
||||
if (candidate.name.startsWith(method + "$surround")) {
|
||||
slim.methods.add(candidate);
|
||||
}
|
||||
}
|
||||
final StringWriter out = new StringWriter();
|
||||
slim.accept(new TraceClassVisitor(null, new Textifier(), new PrintWriter(out)));
|
||||
final String stripped = out.toString().replaceAll("(?m)^ *LINENUMBER \\d+ L\\d+\\r?\\n", "");
|
||||
final String actual = normalize(stripped.replaceAll("\\b([a-z]+)\\$[a-z]{3,}[0-9a-f]{3,}\\$", "$1\\$\\$").replaceAll("\\bmd[0-9a-f]+\\$jvmdowngrader\\$", "md\\$jvmdowngrader\\$"));
|
||||
final Path file = Paths.get(System.getProperty("surround.golden.dir")).resolve(golden + ".asm");
|
||||
try {
|
||||
if (!Files.exists(file)) {
|
||||
Files.write(file, actual.getBytes(StandardCharsets.UTF_8));
|
||||
fail("Created golden " + file + ". Inspect it, then re-run.");
|
||||
}
|
||||
assertEquals(normalize(new String(Files.readAllBytes(file), StandardCharsets.UTF_8)), actual, "output changed; inspect the diff before updating " + file);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalize(String text) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
for (String line : text.replace("\r\n", "\n").split("\n", -1)) {
|
||||
result.append(line.replaceAll("\\s+$", "").replaceAll("(\\$jvmdowngrader\\$concat\\$[\\w$]*?\\$\\d+)\\$[0-9a-f]+\\b", "$1")).append('\n');
|
||||
}
|
||||
return result.toString().trim() + "\n";
|
||||
}
|
||||
|
||||
static final Object VOID = null;
|
||||
|
||||
static Arguments scenario(String target, String name, String method, Object[] inputs, Object[] outputs, String... events) {
|
||||
return Arguments.of(Named.of(name, method), target, inputs, outputs, events);
|
||||
}
|
||||
|
||||
static Object[] of(Object... values) {
|
||||
return values;
|
||||
}
|
||||
|
||||
static List<Object> args(Object... values) {
|
||||
return Arrays.asList(values);
|
||||
}
|
||||
|
||||
static void assertScenario(String target, String method, Object[] inputs, Object[] outputs, String[] events) throws Exception {
|
||||
Trace.reset();
|
||||
for (int i = 0; i < inputs.length; i++) {
|
||||
final Object[] args = inputs[i] instanceof List<?> list ? list.toArray() : new Object[]{inputs[i]};
|
||||
if (outputs[i] instanceof Class<?> type && Throwable.class.isAssignableFrom(type)) {
|
||||
final InvocationTargetException thrown = assertThrows(InvocationTargetException.class, () -> call(target, method, args));
|
||||
assertEquals(outputs[i], thrown.getCause().getClass());
|
||||
} else {
|
||||
assertEquals(outputs[i], call(target, method, args));
|
||||
}
|
||||
}
|
||||
Trace.assertEvents(events);
|
||||
}
|
||||
|
||||
private static final boolean MIXINEXTRAS = Boolean.getBoolean("surround.test.mixinextras");
|
||||
private static final boolean ADDITIONS_FIRST = Boolean.getBoolean("surround.test.additionsFirst");
|
||||
private static final boolean OBF = Boolean.getBoolean("surround.test.obf");
|
||||
|
||||
private static IMixinTransformer boot() {
|
||||
Launch.blackboard = new HashMap<>();
|
||||
Launch.classLoader = new LaunchClassLoader(classpath());
|
||||
if (OBF) {
|
||||
Launch.classLoader.addTransformerExclusion(LocalSortingTransformer.class.getName());
|
||||
Launch.classLoader.registerTransformer(LocalSortingTransformer.class.getName());
|
||||
if (Launch.classLoader.getTransformers().stream().noneMatch(t -> t.getClass().getName().equals(LocalSortingTransformer.class.getName()))) {
|
||||
throw new IllegalStateException("LocalSortingTransformer was not registered");
|
||||
}
|
||||
}
|
||||
|
||||
if (!MIXINEXTRAS) {
|
||||
SurroundBootstrap.init();
|
||||
}
|
||||
MixinBootstrap.init();
|
||||
if (MIXINEXTRAS) {
|
||||
if (ADDITIONS_FIRST) {
|
||||
SurroundBootstrap.init();
|
||||
}
|
||||
MixinExtrasBootstrap.init();
|
||||
if (!ADDITIONS_FIRST) {
|
||||
SurroundBootstrap.init();
|
||||
}
|
||||
}
|
||||
Mixins.addConfiguration("mixins.surround.test.json");
|
||||
if (!OBF) {
|
||||
Mixins.addConfiguration(RejectionCases.compileConfig(MIXINEXTRAS));
|
||||
}
|
||||
if (MIXINEXTRAS) {
|
||||
Mixins.addConfiguration("mixins.surround.mixinextras.test.json");
|
||||
}
|
||||
|
||||
final EnvironmentStateTweaker tweaker = new EnvironmentStateTweaker();
|
||||
tweaker.injectIntoClassLoader(Launch.classLoader);
|
||||
tweaker.getLaunchArguments();
|
||||
|
||||
return active();
|
||||
}
|
||||
|
||||
private static IMixinTransformer active() {
|
||||
final Object transformer = MixinEnvironment.getCurrentEnvironment().getActiveTransformer();
|
||||
if (!(transformer instanceof IMixinTransformer mixinTransformer)) {
|
||||
throw new IllegalStateException("no active mixin transformer, got " + transformer);
|
||||
}
|
||||
return mixinTransformer;
|
||||
}
|
||||
|
||||
private static URL[] classpath() {
|
||||
final List<URL> urls = new ArrayList<>();
|
||||
for (String entry : System.getProperty("java.class.path").split(File.pathSeparator)) {
|
||||
try {
|
||||
urls.add(new File(entry).toURI().toURL());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalStateException(entry, e);
|
||||
}
|
||||
}
|
||||
return urls.toArray(new URL[urls.size()]);
|
||||
}
|
||||
|
||||
static byte[] read(String binaryName) {
|
||||
try {
|
||||
final byte[] bytes = Launch.classLoader.getClassBytes(binaryName);
|
||||
if (bytes == null) {
|
||||
throw new IllegalStateException("missing " + binaryName);
|
||||
}
|
||||
return bytes;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(binaryName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> define(String name, byte[] bytes) {
|
||||
try {
|
||||
return new DefiningClassLoader(name, bytes).loadClass(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DefiningClassLoader extends ClassLoader {
|
||||
|
||||
private final String name;
|
||||
private final byte[] bytes;
|
||||
|
||||
DefiningClassLoader(String name, byte[] bytes) {
|
||||
super(MixinTestBootstrap.class.getClassLoader());
|
||||
this.name = name;
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
if (!this.name.equals(name)) {
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
Class<?> loaded = findLoadedClass(name);
|
||||
if (loaded == null) {
|
||||
loaded = defineClass(name, this.bytes, 0, this.bytes.length);
|
||||
}
|
||||
if (resolve) {
|
||||
resolveClass(loaded);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
final class RejectionCases {
|
||||
|
||||
static final String PACKAGE = "rejection";
|
||||
static final String CONFIG = "mixins.surround.rejection.test.json";
|
||||
static final String ABS = "at = @At(value = \"INVOKE\", target = \"Ljava/lang/Math;abs(I)I\")";
|
||||
static final String WHOLE_ENTRY = "@Surround(method = \"run\") private void invalid$enter(int a) {}";
|
||||
static final String FINALLY = " @Surround.Finally private void invalid$always() {}";
|
||||
static final String RUN = cls("public int run(int a) { return Math.abs(a); }");
|
||||
static final String IN_NEW = cls("public int run(int a) { return new StringBuilder(Math.abs(a)).length(); }");
|
||||
|
||||
static final Case[] CASES = {
|
||||
new Case("Initializer", "@Surround invalid$enter cannot target the initializer", RUN, mixin("@Surround(method = \"<init>()V\") private void invalid$enter() {}")),
|
||||
new Case("Staticness", "must be non-static to match the entry handler", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally private static void invalid$always() {}")),
|
||||
new Case("CarriedThrowable", "cannot mark its caught Throwable parameter @Surround.Carry", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " @Surround.Carry Throwable state = null; }" + " @Surround.Catch private void invalid$caught(@Surround.Carry Throwable e) {}")),
|
||||
new Case("LocalCall", "is @Surround.Local java.lang.String, which matches no single local at the call", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a, @Surround.Local String s) {}")),
|
||||
new Case("HandleInsideNew", "declares handle = true, but the call is inside a constructor call's arguments", IN_NEW, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {}" + " @Surround.Catch(handle = true) private int invalid$caught(Throwable error) { return 0; }")),
|
||||
new Case("SkippedRequired", "can skip a int result but has no @Surround.Skipped", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {" + " @Surround.Skip boolean skip = a > 0; }")),
|
||||
new Case("Shift", "@Surround invalid$enter has @At(\"INVOKE\") with a non-default shift or by, which moves the match off the call", RUN, mixin("@Surround(method = \"run\", at = @At(value = \"INVOKE\", target = \"Ljava/lang/Math;abs(I)I\", shift = At.Shift.AFTER)) private void invalid$enter() {}")),
|
||||
new Case("AbstractTarget", "cannot target the abstract or native method", "public abstract class $T { public abstract int run(int a); }", "@Mixin(value = $T.class, remap = false) public abstract class $M {" + " @Surround(method = \"run\") private void invalid$enter(int a) {} }"),
|
||||
new Case("InitAt", "at must select a method call, not the constructor call java/lang/StringBuilder.<init>()V", cls("public Object run(int a) { return new StringBuilder(); }"), mixin("@Surround(method = \"run\", at = @At(value = \"INVOKE\", target = \"Ljava/lang/StringBuilder;<init>()V\")) private void invalid$enter() {}")),
|
||||
new Case("CatchingEntry", "must not catch exceptions when it surrounds a call inside a constructor call's arguments", IN_NEW, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {" + " try { Math.abs(a); } catch (RuntimeException e) { Math.abs(0); } }" + FINALLY)),
|
||||
new Case("TooManyOperands", "entry handler has more parameters than call java/lang/Math.abs(I)I", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a, int b) {}")),
|
||||
new Case("SkipNotAssigned", "has a @Surround.Skip local in slot 2 that is not definitely assigned", cls("public void run(int a) {}"), mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " @Surround.Skip boolean skip; if (a > 0) { skip = true; String.valueOf(a); } }")),
|
||||
new Case("Collision", "exit handler 'surround$exit' collides with surround$exit()V in " + PACKAGE + "/Collision, which is merged from " + PACKAGE + ".mixin.CollisionMixin2", RUN, "@Mixin(value = $T.class, priority = 900, remap = false) public class $M {" + " @Surround(method = \"run\") private void surround$enter(int a) {}" + " @Surround.Finally private void surround$exit() {} }", "@Mixin(value = $T.class, priority = 1100, remap = false) public class $M {" + " private void surround$exit() {} }"),
|
||||
new Case("MultipleRoles", "can be only one of @Surround.Catch, @Surround.Finally, @Surround.Skipped and @Surround.Return", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch @Surround.Finally private void invalid$exit(Throwable e) {}")),
|
||||
new Case("DuplicateId", "duplicate @Surround the default id; ids must be unique per mixin", RUN, mixin(WHOLE_ENTRY + " @Surround(method = \"run\") private void invalid$enter2(int a) {}")),
|
||||
new Case("OrphanExit", "no @Surround in this mixin declares it", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally(\"typo\") private void invalid$always() {}")),
|
||||
new Case("CarryOnEntryParameter", "only the entry handler's own locals can be carried", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(@Surround.Carry int a) {}")),
|
||||
new Case("CarryIdNotDeclared", "asks for the carried local 'frist'", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry(\"first\") int first = 1; }" + " @Surround.Finally private void invalid$always(@Surround.Carry(\"frist\") int typo) {}")),
|
||||
new Case("NoCarryOfKind", "matches no carried local of that kind", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry int count = 1; }" + " @Surround.Finally private void invalid$always(@Surround.Carry float missing) {}")),
|
||||
new Case("AmbiguousCarry", "matches 2 carried locals of that kind", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry int first = 1; @Surround.Carry int second = 2; }" + " @Surround.Finally private void invalid$always(@Surround.Carry int either) {}")),
|
||||
new Case("CarryReadAsDifferentType", "but another exit handler reads it as java.lang.String", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry Object value = null; }" + " @Surround.Catch private void invalid$caught(java.io.IOException e, @Surround.Carry String value) {}" + " @Surround.Finally private void invalid$always(@Surround.Carry Integer value) {}")),
|
||||
new Case("AmbiguousWideLocal", "shares its scope with another", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry long first; long second; try { first = a; second = a + 1L; } catch (RuntimeException e) { throw e; } }")),
|
||||
new Case("DuplicateCarryId", "declares two @Surround.Carry locals named 'x'", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry(\"x\") int first = 1; @Surround.Carry(\"x\") long second = 2L; }")),
|
||||
new Case("CarryInsideType", "@Surround.Carry must annotate the local itself, not part of its type", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { java.util.List<@Surround.Carry String> l = null; }")),
|
||||
new Case("DuplicateSkip", "declares two @Surround.Skip locals", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Skip boolean first = a > 0; @Surround.Skip boolean second = a < 0; }")),
|
||||
new Case("SkipNotIntLike", "it must be boolean or int-like", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Skip long skip = a; }")),
|
||||
new Case("SkippedWithoutSkip", "but declares no @Surround.Skip local, so nothing is ever skipped", RUN, mixin(WHOLE_ENTRY + " @Surround.Skipped private int invalid$skipped(int a) { return 0; }")),
|
||||
new Case("CarryScopedEarly", "goes out of scope before the entry handler ends", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " { @Surround.Carry int scoped = a; String.valueOf(scoped); } int reused = a + 1; String.valueOf(reused); }" + " @Surround.Finally private void invalid$always(@Surround.Carry int value) {}")),
|
||||
new Case("EarlyReturn", "every path falls through to its end", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " if (a > 0) { return; } String.valueOf(a); }" + FINALLY)),
|
||||
new Case("PartialAfterInt", "is not definitely assigned when the entry handler ends", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " { int tmp = a + 5; String.valueOf(tmp); }" + " @Surround.Carry int flag; if (a > 0) { flag = 1; String.valueOf(flag); } }" + " @Surround.Finally private void invalid$always(@Surround.Carry int flag) {}")),
|
||||
new Case("AssignsParameter", "must not assign its parameters", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { a = a + 1; }" + FINALLY)),
|
||||
new Case("LocalOnWholeMethod", "is @Surround.Local, which only applies when @Surround.at selects a call", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally private void invalid$always(@Surround.Local int b) {}")),
|
||||
new Case("IncrementsParameter", "must not assign its parameters", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { a++; }" + FINALLY)),
|
||||
new Case("NonCallAt", "@Surround invalid$enter has @At(\"HEAD\"), which does not select a method call", RUN, mixin("@Surround(method = \"run\", at = @At(\"HEAD\")) private void invalid$enter() {}")),
|
||||
new Case("CancellableExit", "parameter 0 ('ci') of 'invalid$always' is annotated @Cancellable, which a @Surround handler cannot resolve", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally private void invalid$always(@Cancellable Object ci) {}")),
|
||||
new Case("DuplicateFinally", "declares more than one @Surround.Finally", RUN, mixin(WHOLE_ENTRY + FINALLY + " @Surround.Finally private void invalid$always2() {}")),
|
||||
new Case("ReturnWrongType", "must return java.lang.String to match what it surrounds, not int", cls("public String run(String s) { return s.trim(); }"), mixin("@Surround(method = \"run\", at = @At(value = \"INVOKE\", target = \"Ljava/lang/String;trim()Ljava/lang/String;\")) private void invalid$enter(String s) {}" + " @Surround.Return private int invalid$ret(int result) { return result; }")),
|
||||
new Case("ReturnResultNotFirst", "must declare the int result as its first parameter", RUN, mixin(WHOLE_ENTRY + " @Surround.Return private int invalid$ret(String wrong) { return 0; }")),
|
||||
new Case("ReturnCarriedResult", "cannot mark its result parameter @Surround.Carry", RUN, mixin(WHOLE_ENTRY + " @Surround.Return private int invalid$ret(@Surround.Carry int result) { return result; }")),
|
||||
new Case("NonVoidFinally", "must return void; an exit handler cannot suppress or replace anything", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally private int invalid$always() { return 1; }")),
|
||||
new Case("NonVoidEntry", "must return void; carry state out with @Surround.Carry locals instead", RUN, mixin("@Surround(method = \"run\") private long invalid$enter(int a) { return 0L; }")),
|
||||
new Case("CatchNotThrowable", "parameter 0 ('notAnError') catches java.lang.String, which is not a java.lang.Throwable subtype", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch private void invalid$caught(String notAnError) {}")),
|
||||
new Case("DuplicateCatch", "is a second @Surround.Catch for java.io.IOException", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch private void invalid$first(java.io.IOException e) {} @Surround.Catch private void invalid$second(java.io.IOException e) {}")),
|
||||
new Case("CatchWithoutParameters", "must declare the Throwable it catches as its first parameter", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch private void invalid$caught() {}")),
|
||||
new Case("AbstractEntry", "must not be abstract", RUN, "@Mixin(value = $T.class, remap = false) public abstract class $M { @Surround(method = \"run\") protected abstract void invalid$enter(int a); }"),
|
||||
new Case("AbstractCatch", "must not be abstract", RUN, "@Mixin(value = $T.class, remap = false) public abstract class $M { " + WHOLE_ENTRY + " @Surround.Catch protected abstract void invalid$caught(Throwable t); }"),
|
||||
new Case("MisorderedLocalAfterCarry", "parameter 1 ('y') is out of order; declare operands first, then @Surround.Local, then @Surround.Carry parameters", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {}" + " @Surround.Finally private void invalid$always(@Surround.Carry int x, @Surround.Local int y) {}")),
|
||||
new Case("LocalAndCarry", "parameter 0 ('a') cannot be both @Surround.Local and @Surround.Carry", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {}" + " @Surround.Finally private void invalid$always(@Surround.Local @Surround.Carry int a) {}")),
|
||||
new Case("LocalCaught", "cannot mark its caught Throwable parameter @Surround.Local", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {}" + " @Surround.Catch private void invalid$caught(@Surround.Local Throwable e) {}")),
|
||||
new Case("HandleWrongReturn", "must return java.lang.String to match what it surrounds, not int", cls("public String run(String s) { return s.trim(); }"), mixin("@Surround(method = \"run\", at = @At(value = \"INVOKE\", target = \"Ljava/lang/String;trim()Ljava/lang/String;\")) private void invalid$enter(String s) {}" + " @Surround.Catch(handle = true) private int invalid$caught(Throwable e, String s) { return 0; }")),
|
||||
new Case("HandleWithoutAt", "declares handle = true, which only applies when @Surround.at selects a call", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch(handle = true) private void invalid$caught(Throwable e) {}")),
|
||||
new Case("SliceWithoutAt", "declares @Surround.slice, which only applies when @Surround.at selects a call", RUN, mixin("@Surround(method = \"run\", slice = @Slice(from = @At(\"HEAD\"))) private void invalid$enter(int a) {}")),
|
||||
new Case("EntryOperandMismatch", "parameter 0 ('notAnInt') is java.lang.String, which does not match the int operand of call java/lang/Math.abs(I)I", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(String notAnInt) {}")),
|
||||
new Case("OrphanWithoutSurround", "@Surround exit handler 'invalid$always' refers to the default id but no @Surround in this mixin declares it; this mixin has no @Surround at all", RUN, mixin(WHOLE_ENTRY), mixin("@Surround.Finally private void invalid$always() {}"))
|
||||
};
|
||||
|
||||
static final Case SUGAR = new Case("Sugar", "@Surround invalid$enter one of its parameters is annotated @Local, @Share or @Cancellable, which a @Surround handler cannot resolve", cls("public int run(int a) { int local = a + 1; return Math.abs(local); }"), mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int local, @Local(ordinal = 0) int a) {}"));
|
||||
|
||||
static final Case REQUIRE_ZERO_MISS = new Case("RequireZeroMiss", "", RUN, mixin("@Surround(method = \"run\", require = 0, at = @At(value = \"INVOKE\", target = \"Ljava/lang/Math;max(II)I\")) private void invalid$enter(int a, int b) {}" + FINALLY));
|
||||
|
||||
static String compileConfig(boolean mixinExtras) {
|
||||
final List<Case> cases = new ArrayList<>();
|
||||
if (mixinExtras) {
|
||||
cases.add(SUGAR);
|
||||
} else {
|
||||
cases.addAll(Arrays.asList(CASES));
|
||||
cases.add(REQUIRE_ZERO_MISS);
|
||||
}
|
||||
final List<Unit> units = new ArrayList<>();
|
||||
final List<String> mixins = new ArrayList<>();
|
||||
for (Case c : cases) {
|
||||
units.add(new Unit(PACKAGE + "." + c.name, "package " + PACKAGE + "; " + c.target.replace("$T", c.name)));
|
||||
for (int i = 0; i < c.mixins.length; i++) {
|
||||
final String name = c.name + "Mixin" + (i + 1);
|
||||
mixins.add('"' + name + '"');
|
||||
units.add(new Unit(PACKAGE + ".mixin." + name, "package " + PACKAGE + ".mixin; import " + PACKAGE + ".*;" + " import com.gtnewhorizons.angelica.experimental.surround.Surround;" + " import com.llamalad7.mixinextras.sugar.Cancellable;" + " import com.llamalad7.mixinextras.sugar.Local;" + " import org.spongepowered.asm.mixin.Mixin;" + " import org.spongepowered.asm.mixin.injection.At;" + " import org.spongepowered.asm.mixin.injection.Slice;" + c.mixins[i].replace("$T", c.name).replace("$M", name)));
|
||||
}
|
||||
}
|
||||
try {
|
||||
final Path dir = Paths.get("rejection-classes").toAbsolutePath();
|
||||
if (Files.exists(dir)) {
|
||||
try (Stream<Path> stale = Files.walk(dir)) {
|
||||
stale.sorted(Comparator.reverseOrder()).forEach(path -> path.toFile().delete());
|
||||
}
|
||||
}
|
||||
final Path sources = Files.createDirectories(dir.resolve("src"));
|
||||
final List<String> command = new ArrayList<>(Arrays.asList(System.getProperty("surround.javac"), "-classpath", System.getProperty("java.class.path"), "-d", dir.toString(), "-proc:none", "-g", "--release", "8", "-Xlint:-options"));
|
||||
for (Unit unit : units) {
|
||||
final Path file = sources.resolve(unit.className().replace('.', '/') + ".java");
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.write(file, unit.source().getBytes(StandardCharsets.UTF_8));
|
||||
command.add(file.toString());
|
||||
}
|
||||
final Process javac = new ProcessBuilder(command).redirectErrorStream(true).start();
|
||||
final String output = new String(javac.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
if (javac.waitFor() != 0) {
|
||||
throw new IllegalStateException(output);
|
||||
}
|
||||
Files.write(dir.resolve(CONFIG), ("{\"required\": true, \"minVersion\": \"0.8\", \"package\": \"" + PACKAGE + ".mixin\"," + " \"compatibilityLevel\": \"JAVA_8\", \"mixins\": " + mixins + "}").getBytes(StandardCharsets.UTF_8));
|
||||
Launch.classLoader.addURL(dir.toUri().toURL());
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return CONFIG;
|
||||
}
|
||||
|
||||
private static String cls(String members) {
|
||||
return "public class $T { " + members + " }";
|
||||
}
|
||||
|
||||
private static String mixin(String members) {
|
||||
return "@Mixin(value = $T.class, remap = false) public class $M { " + members + " }";
|
||||
}
|
||||
|
||||
private record Unit(String className, String source) {
|
||||
}
|
||||
|
||||
static final class Case {
|
||||
|
||||
final String name;
|
||||
final String expected;
|
||||
final String target;
|
||||
final String[] mixins;
|
||||
|
||||
Case(String name, String expected, String target, String... mixins) {
|
||||
this.name = name;
|
||||
this.expected = expected;
|
||||
this.target = target;
|
||||
this.mixins = mixins;
|
||||
}
|
||||
|
||||
void assertRejected() {
|
||||
final String messages = causeChain(transformExpectingFailure(PACKAGE + "." + this.name));
|
||||
assertTrue(messages.contains(this.expected), messages);
|
||||
}
|
||||
|
||||
private static Throwable transformExpectingFailure(String binaryName) {
|
||||
try {
|
||||
MixinTestBootstrap.transformer().transformClassBytes(binaryName, binaryName, MixinTestBootstrap.read(binaryName));
|
||||
} catch (Throwable t) {
|
||||
return t;
|
||||
}
|
||||
throw new AssertionError(binaryName + " transformed without the expected failure");
|
||||
}
|
||||
|
||||
private static String causeChain(Throwable thrown) {
|
||||
final StringBuilder all = new StringBuilder();
|
||||
for (Throwable t = thrown; t != null; t = t.getCause()) {
|
||||
all.append(t).append('\n');
|
||||
}
|
||||
return all.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.junit.jupiter.api.Named;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
class RejectionTest {
|
||||
|
||||
static Stream<Named<RejectionCases.Case>> rejections() {
|
||||
return Arrays.stream(RejectionCases.CASES).map(c -> Named.of(c.name, c));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("rejections")
|
||||
void anInvalidMixinIsRejectedAtLoad(RejectionCases.Case rejection) {
|
||||
rejection.assertRejected();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aRequireZeroCallSurroundThatMatchesNoCallIsNotRejected() {
|
||||
assertDoesNotThrow(() -> MixinTestBootstrap.transform(RejectionCases.PACKAGE + "." + RejectionCases.REQUIRE_ZERO_MISS.name));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("mixinextras")
|
||||
void mixinExtrasSugarOnACallSiteEntryHandlerIsRejected() {
|
||||
RejectionCases.SUGAR.assertRejected();
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.CALL;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.VOID;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.WHOLE;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.args;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.of;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.scenario;
|
||||
|
||||
class ScenarioTest {
|
||||
|
||||
static Stream<Arguments> scenarios() {
|
||||
return Stream.of(
|
||||
scenario(WHOLE, "a surround encloses the body and every other injector", "compute", of(7), of(14), "enter 7 1", "head 7", "body 7", "tail 14", "exit 1"),
|
||||
scenario(WHOLE, "the narrowest catch handler wins and finally runs when the body throws", "boom", of(args()), of(IllegalStateException.class), "enterBoom 1", "boomHead", "boom", "caught 1 boom", "exitBoom 1"),
|
||||
scenario(WHOLE, "an instance monitor covers the handlers and the body", "lockedInstance", of(7), of(14), "enterInstance true", "body true", "exitInstance true"),
|
||||
scenario(WHOLE, "a unique exit handler renamed on collision still binds", "run", of(args()), of(VOID), "enter", "run", "target exit", "handler exit"),
|
||||
scenario(WHOLE, "a lower priority mixin is the inner surround", "priority", of(5), of(60), "enterHigh", "enterLow", "body 5", "returnLow 5", "exitLow", "returnHigh 6", "exitHigh"),
|
||||
scenario(WHOLE, "static wide arguments", "wideBody", of(args(2.5d, 5L)), of(7.5d), "enter 1", "body", "exit 1"),
|
||||
scenario(WHOLE, "a throwing entry handler skips the body and the exit handler", "throwingEntry", of(5), of(IllegalArgumentException.class), "enterThrows 5"),
|
||||
scenario(WHOLE, "a throwing finally replaces the return value", "throwingFinally", of(args(9, 4)), of(UnsupportedOperationException.class), "enter 1", "gt", "exitThrows 1"),
|
||||
scenario(WHOLE, "pre-existing exception handlers still fire", "caught", of(4, -4), of(4, -1), "enter 1", "inner-finally", "exit 1", "enter 2", "caught neg", "inner-finally", "exit 2"),
|
||||
scenario(WHOLE, "three kinds bind by kind, not position", "carryThree", of(7), of(14), "enter 8 107 L7", "body 7", "exit 8 107 L7"),
|
||||
scenario(WHOLE, "frame naming an unbuilt object", "carryUninitialized", of(5), of(10), "enter pos", "body 5", "exit pos"),
|
||||
scenario(WHOLE, "entry handler's own try-catch", "carryGuarded", of(-3), of(-6), "recovered", "enter -1", "body -3", "exit -1"),
|
||||
scenario(WHOLE, "assigned in both branches at the end", "carrySplitAtEnd", of(-3), of(-6), "body -3", "exit 2"),
|
||||
scenario(WHOLE, "wide carry ahead of an int across a frame", "carryWideBeforeFrame", of(3), of(6), "positive", "body 3", "exit 3 4"),
|
||||
scenario(WHOLE, "array locals", "carryArrays", of(3), of(6), "body 3", "exit n3 3"),
|
||||
scenario(WHOLE, "widths and an uncarried local between carried ones survive the handler frame", "throwWide", of(3), of(IllegalStateException.class), "enter 4 1.5 3 s3", "body 3", "caught 4 1.5 3", "exit 4 1.5 3"),
|
||||
scenario(WHOLE, "a reference carry is cast to each exit handler's type and a throwing catch handler still runs finally", "throwObject", of(3), of(UnsupportedOperationException.class), "enter o3", "body 3", "caught 2 o3", "exit 2 o3"),
|
||||
scenario(WHOLE, "a reused slot carries the local's value", "reused", of(3), of(6), "tmp 8", "enter 10", "body 3", "exit 10"),
|
||||
scenario(CALL, "a static target takes static handlers around a static callee", "staticTarget", of(4), of(8), "enterStaticTarget 4", "twiceStatic 4", "exitStaticTarget 4 8"),
|
||||
scenario(CALL, "an interface callee is typed as the interface or a supertype", "interfaceCall", of(3), of(4), "enterInterface 3 true", "apply 3", "exitInterface true"),
|
||||
scenario(CALL, "a private callee's receiver is the target itself", "privateCall", of(3), of(4), "enterPrivate true 3", "priv 3", "exitPrivate true 3"),
|
||||
scenario(CALL, "every match is surrounded separately", "all", of(2), of(18), "enterAll 2 1", "twice 2", "exitAll 2 1", "enterAll 3 2", "twice 3", "exitAll 3 2", "enterAll 4 3", "twice 4", "exitAll 4 3"),
|
||||
scenario(CALL, "a slice restricts the matches", "sliced", of(2), of(10), "twice 2", "marker", "enterSliced 3", "twice 3", "exitSliced 3"),
|
||||
scenario(CALL, "a throwing entry handler runs no exit handler and skips the call", "throwingEntry", of(args()), of(IllegalArgumentException.class), "enterThrowingEntry 1"),
|
||||
scenario(CALL, "inject before, modify arg and inject after stay outside", "composed", of(5), of(210), "before 5", "modifyArg 5", "enterComposed 105", "twice 105", "exitComposed 105", "after 5"),
|
||||
scenario(WHOLE, "a void target skips without a skipped handler", "skippedVoid", of(-1, 1), of(VOID, VOID), "enterSkippedVoid -1", "exitSkippedVoid", "enterSkippedVoid 1", "skippedVoid 1", "exitSkippedVoid"),
|
||||
scenario(WHOLE, "an entry handler may call the target directly and then skip", "reentrant", of(1), of(24), "enterReentrant 11 false", "reentrant 11", "exitReentrant 11", "enterReentrant 1 true", "exitReentrant 1"),
|
||||
scenario(WHOLE, "return does not run after a skip and a reference skipped value is returned", "skipAndReturn", of("", "a", "x"), of(null, "a!?", IllegalStateException.class), "enterSkipAndReturn ", "skippedSkipAndReturn", "exitSkipAndReturn ", "enterSkipAndReturn a", "skipAndReturn a", "returnSkipAndReturn a!", "exitSkipAndReturn a", "enterSkipAndReturn x", "skipAndReturn x", "returnSkipAndReturn x!", "exitSkipAndReturn x"),
|
||||
scenario(WHOLE, "an entry handler may catch and rethrow around a direct call", "entryCatches", of(3, -1), of(6, IllegalStateException.class), "entryCatches 3", "exitEntryCatches false", "enterEntryCatches 3", "entryCatches 3", "exitEntryCatches true", "entryCatches -1", "exitEntryCatches false", "entryRethrow negative -1"),
|
||||
scenario(CALL, "a local is selected by name between operands and carries", "byName", of(3), of(49), "enterByName 23 1", "twice 13", "exitByName 13 23 1"),
|
||||
scenario(CALL, "wide locals bind", "wideLocals", of(3), of(31L), "enterWideLocals 30 1.5", "wide 30 1.5", "exitWideLocals 1.5 30"),
|
||||
scenario(CALL, "a handling catch replaces the result over the values below", "valueBelow", of(-3, 4), of(67, 84), "enterValueBelow -3 1", "explode -3", "handleValueBelow -3 1", "exitValueBelow -3 1", "enterValueBelow 4 2", "explode 4", "exitValueBelow 4 2"),
|
||||
scenario(CALL, "a skipped handler supplies the value of a skipped call", "valueSkip", of(-3, 3), of(-300, 6), "enterValueSkip -3 1", "skippedValueSkip -3 1", "exitValueSkip 1", "enterValueSkip 3 2", "twice 3", "exitValueSkip 2"),
|
||||
scenario(CALL, "a void return handler observes and does not run after a handled exception", "observed", of(2, -1), of(VOID, VOID), "enterObserved 2", "maybeBoom 2", "observe 2", "exitObserved 2", "enterObserved -1", "maybeBoom -1", "handleObserved -1", "exitObserved -1"),
|
||||
scenario(CALL, "a handled value bypasses the return handler", "returnAndHandle", of(4, -1), of(80, 7), "enterReturnAndHandle 4", "explode 4", "returnReturnAndHandle 8", "exitReturnAndHandle", "enterReturnAndHandle -1", "explode -1", "handleReturnAndHandle", "exitReturnAndHandle"),
|
||||
scenario(CALL, "the skip path is covered by catch and finally but not by return", "skipFinallyCatch", of(2, -3, 0), of(1004, 3, IllegalStateException.class), "enterSfc 2", "explode 2", "returnSfc 4", "exitSfc 2", "enterSfc -3", "skippedSfc -3", "exitSfc -3", "enterSfc 0", "skippedSfc 0", "caughtSfc zero", "exitSfc 0"),
|
||||
scenario(CALL, "an outer skip runs no inner handler and return handlers chain inner to outer", "nestedSkip", of(-2, 2), of(-1, 50), "enterNestedOuter -2", "skippedNestedOuter", "exitNestedOuter -2", "enterNestedOuter 2", "enterNestedInner 2", "twice 2", "returnNestedInner 4", "exitNestedInner 2", "returnNestedOuter 5", "exitNestedOuter 2"),
|
||||
scenario(CALL, "a skip skips the redirect handler inside", "redirectedSkip", of(-1, 3), of(0, 7), "enterRedirectedSkip -1", "exitRedirectedSkip -1", "enterRedirectedSkip 3", "redirectSkip 3", "twice 3", "exitRedirectedSkip 3"),
|
||||
scenario(CALL, "a final skip derived from a carry computed by a unique helper binds", "helperSkip", of(-4, 4), of(-1, 8), "part -4", "enterHelper -1", "skippedHelper", "exitHelper -1", "part 4", "enterHelper 4", "twice 4", "exitHelper 4"),
|
||||
scenario(CALL, "wide operands skip and return with an int skip flag", "wideSkip", of(args(-2L, 3.0d), args(5L, 2.5d)), of(-19L, 108L), "enterWideSkip -2 3.0", "skippedWideSkip -2 1.5", "exitWideSkip -2 3.0", "enterWideSkip 5 2.5", "wide 5 2.5", "returnWideSkip 7 1.25", "exitWideSkip 5 2.5"),
|
||||
scenario(CALL, "an entry-only call surround needs no exit handler even with a skip", "entryOnlySkip", of(1, -1), of(1, -1), "enterOnlySkip 1", "enterOnlySkip -1", "render -1"),
|
||||
scenario(CALL, "an entry-only whole-method surround", "entryOnlyWhole", of(args()), of(VOID), "enterOnlyWhole", "entryOnlyWhole"),
|
||||
scenario(CALL, "a skip restores the values below the call when the entry catches", "skipBelowRestored", of(-2, 3, 200), of(980, 1006, 1400), "enterSkipBelowRestored -2", "skippedSkipBelowRestored -2", "exitSkipBelowRestored -2", "enterSkipBelowRestored 3", "twice 3", "exitSkipBelowRestored 3", "entryCaughtSkipBelowRestored 200", "twice 200", "exitSkipBelowRestored 200"),
|
||||
scenario(CALL, "an inner skip completes normally for an outer return handler", "innerSkipOuterReturn", of(-2, 3), of(-199, 7), "enterOuterReturn -2", "enterInnerSkip -2", "skippedInnerSkip -2", "exitInnerSkip -2", "returnOuterReturn -200", "exitOuterReturn -2", "enterOuterReturn 3", "enterInnerSkip 3", "twice 3", "exitInnerSkip 3", "returnOuterReturn 6", "exitOuterReturn 3"),
|
||||
scenario(CALL, "a call inside a constructor call's arguments can be skipped", "skipInsideNew", of(-2, 3), of(-20, 6), "enterSkipInsideNew -2", "skippedSkipInsideNew -2", "exitSkipInsideNew -2", "enterSkipInsideNew 3", "twice 3", "exitSkipInsideNew 3"),
|
||||
scenario(CALL, "a call surround skips inside a body moved by a whole-method surround", "movedBody", of(-2, 3), of(990, 1008), "enterMovedWhole -2", "enterMovedCall -1 -2", "skippedMovedCall -1 -2", "exitMovedCall -1 -2", "exitMovedWhole -2", "enterMovedWhole 3", "enterMovedCall 4 3", "twice 4", "exitMovedCall 4 3", "exitMovedWhole 3"),
|
||||
scenario(WHOLE, "a branching entry handler around the whole method", "branching", of(-3), of(-6), "enterBranching negative", "body -3", "exitBranching negative")
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("scenarios")
|
||||
void scenarios(String method, String target, Object[] inputs, Object[] outputs, String[] events) throws Exception {
|
||||
MixinTestBootstrap.assertScenario(target, method, inputs, outputs, events);
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.LabelNode;
|
||||
import org.spongepowered.asm.lib.tree.LineNumberNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.WHOLE;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.call;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class WholeMethodTest {
|
||||
|
||||
@BeforeEach
|
||||
void resetTrace() {
|
||||
Trace.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneEntryHandlerCanSurroundSeveralTargets() throws Exception {
|
||||
call(WHOLE, "alpha");
|
||||
call(WHOLE, "beta");
|
||||
Trace.assertEvents("enterBoth 1", "alpha", "exitBoth 1", "enterBoth 2", "beta", "exitBoth 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theWrapperKeepsTheEntryHandlersLinesAndTheBodyKeepsItsOwn() {
|
||||
final ClassNode merged = MixinTestBootstrap.transformedNode(WHOLE);
|
||||
final List<Integer> entryLines = lines(bySuffix(merged, "$surround$enterBranching"));
|
||||
assertFalse(entryLines.isEmpty(), "fixture compiled without line numbers");
|
||||
assertEquals(entryLines, lines(MixinTestBootstrap.method(merged, "branching")));
|
||||
assertEquals(lines(MixinTestBootstrap.method(MixinTestBootstrap.originalNode(WHOLE), "branching")), lines(MixinTestBootstrap.method(merged, "branching$surround")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aReusedSlotNamesTheCarriedLocal() {
|
||||
final MethodNode wrapper = MixinTestBootstrap.method(MixinTestBootstrap.transformedNode(WHOLE), "reused");
|
||||
LabelNode last = null;
|
||||
for (AbstractInsnNode insn = wrapper.instructions.getLast(); last == null; insn = insn.getPrevious()) {
|
||||
last = insn instanceof LabelNode label ? label : null;
|
||||
}
|
||||
final List<String> toEnd = new ArrayList<>();
|
||||
for (LocalVariableNode local : wrapper.localVariables) {
|
||||
if (local.end == last) {
|
||||
toEnd.add(local.name);
|
||||
}
|
||||
}
|
||||
assertTrue(toEnd.contains("prev") && !toEnd.contains("tmp"), "locals live to the end: " + toEnd);
|
||||
}
|
||||
|
||||
@Test
|
||||
void theWrapperMatchesTheGoldens() {
|
||||
MixinTestBootstrap.assertGolden(WHOLE, "skipAndReturn", "whole-skip-return");
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledIfSystemProperty(named = "surround.test.obf", matches = "true")
|
||||
void theLaunchClassLoaderTransformersSeeTheMixinClass() throws Exception {
|
||||
MixinTestBootstrap.transform(WHOLE);
|
||||
final Set<?> seen = (Set<?>) Launch.classLoader.loadClass(LocalSortingTransformer.class.getName()).getField("SEEN").get(null);
|
||||
assertTrue(seen.contains("com.gtnewhorizons.angelica.experimental.surround.integration.mixin.MixinWholeTarget"), seen.toString());
|
||||
}
|
||||
|
||||
private static MethodNode bySuffix(ClassNode node, String suffix) {
|
||||
for (MethodNode method : node.methods) {
|
||||
if (method.name.endsWith(suffix)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
final List<String> names = new ArrayList<>();
|
||||
for (MethodNode method : node.methods) {
|
||||
names.add(method.name);
|
||||
}
|
||||
throw new AssertionError("no method ending " + suffix + " in " + names);
|
||||
}
|
||||
|
||||
private static List<Integer> lines(MethodNode method) {
|
||||
final List<Integer> lines = new ArrayList<>();
|
||||
for (AbstractInsnNode insn : method.instructions.toArray()) {
|
||||
if (insn instanceof LineNumberNode ln) {
|
||||
lines.add(ln.line);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
|
||||
public class CallTarget {
|
||||
|
||||
public static final String CALLEE = "Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;";
|
||||
public static final String TWICE = CALLEE + "twice(I)I";
|
||||
|
||||
private final Callee callee = new Callee();
|
||||
private final IntUnaryOperator op = new Callee();
|
||||
private final Render renderer = new Render();
|
||||
|
||||
public static int staticTarget(int a) {
|
||||
return Callee.twiceStatic(a);
|
||||
}
|
||||
|
||||
public int interfaceCall(int a) {
|
||||
return this.op.applyAsInt(a);
|
||||
}
|
||||
|
||||
public int privateCall(int a) {
|
||||
return this.priv(a);
|
||||
}
|
||||
|
||||
private int priv(int a) {
|
||||
Trace.add("priv " + a);
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
public int all(int a) {
|
||||
return this.callee.twice(a) + this.callee.twice(a + 1) + this.callee.twice(a + 2);
|
||||
}
|
||||
|
||||
public int sliced(int a) {
|
||||
final int first = this.callee.twice(a);
|
||||
Callee.marker();
|
||||
return first + this.callee.twice(a + 1);
|
||||
}
|
||||
|
||||
public void throwingEntry() {
|
||||
this.callee.twice(1);
|
||||
}
|
||||
|
||||
public int composed(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public boolean render(Entity entity, double x, double y, double z, float yaw, float partial, boolean flag) {
|
||||
Render render = null;
|
||||
try {
|
||||
render = this.renderer;
|
||||
if (render != null && entity != null) {
|
||||
try {
|
||||
render.doRender(entity, x, y, z, yaw, partial);
|
||||
} catch (Throwable throwable1) {
|
||||
throw new ReportedException("Rendering entity in world", throwable1);
|
||||
}
|
||||
try {
|
||||
render.doRenderShadowAndFire(entity, x, y, z, yaw, partial);
|
||||
} catch (Throwable throwable2) {
|
||||
throw new ReportedException("Post-rendering entity in world", throwable2);
|
||||
}
|
||||
if (flag) {
|
||||
Trace.add("boundingBox " + entity.name);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (Throwable throwable) {
|
||||
throw new ReportedException("Rendering entity (outer)", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public int byName(int a) {
|
||||
final int first = a + 10;
|
||||
final int second = a + 20;
|
||||
return this.callee.twice(first) + second;
|
||||
}
|
||||
|
||||
public long wideLocals(int a) {
|
||||
final long big = a * 10L;
|
||||
final double half = a / 2.0;
|
||||
return this.callee.wide(big, half);
|
||||
}
|
||||
|
||||
public int valueBelow(int a) {
|
||||
return a + this.callee.explode(a) * 10;
|
||||
}
|
||||
|
||||
public int valueSkip(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public void observed(int a) {
|
||||
this.callee.maybeBoom(a);
|
||||
}
|
||||
|
||||
public int returnAndHandle(int a) {
|
||||
return this.callee.explode(a);
|
||||
}
|
||||
|
||||
public int skipFinallyCatch(int a) {
|
||||
return this.callee.explode(a);
|
||||
}
|
||||
|
||||
public int nestedSkip(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int redirectedSkip(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int helperSkip(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public long wideSkip(long a, double b) {
|
||||
return this.callee.wide(a, b) + 1L;
|
||||
}
|
||||
|
||||
public int entryOnlySkip(int a) {
|
||||
this.callee.render(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
public void entryOnlyWhole() {
|
||||
Trace.add("entryOnlyWhole");
|
||||
}
|
||||
|
||||
public int skipBelowRestored(int a) {
|
||||
return 1000 + this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int innerSkipOuterReturn(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int skipInsideNew(int a) {
|
||||
return new AtomicInteger(this.callee.twice(a)).get();
|
||||
}
|
||||
|
||||
public int movedBody(int a) {
|
||||
final int b = a + 1;
|
||||
return 1000 + this.callee.twice(b);
|
||||
}
|
||||
|
||||
public static class Callee implements IntUnaryOperator {
|
||||
|
||||
public int twice(int a) {
|
||||
Trace.add("twice " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public static int twiceStatic(int a) {
|
||||
Trace.add("twiceStatic " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public static void marker() {
|
||||
Trace.add("marker");
|
||||
}
|
||||
|
||||
public long wide(long a, double b) {
|
||||
Trace.add("wide " + a + " " + b);
|
||||
return a + (long) b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int applyAsInt(int a) {
|
||||
Trace.add("apply " + a);
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
public int explode(int a) {
|
||||
Trace.add("explode " + a);
|
||||
if (a < 0) {
|
||||
throw new IllegalStateException("negative " + a);
|
||||
}
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public void maybeBoom(int a) {
|
||||
Trace.add("maybeBoom " + a);
|
||||
if (a < 0) {
|
||||
throw new IllegalStateException("boom " + a);
|
||||
}
|
||||
}
|
||||
|
||||
public void render(int a) {
|
||||
Trace.add("render " + a);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Entity {
|
||||
|
||||
public final String name;
|
||||
public boolean explode;
|
||||
|
||||
public Entity(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Render {
|
||||
|
||||
public void doRender(Entity entity, double x, double y, double z, float yaw, float partial) {
|
||||
Trace.add("doRender " + entity.name + " " + x + " " + partial);
|
||||
if (entity.explode) {
|
||||
throw new RuntimeException("gpu");
|
||||
}
|
||||
}
|
||||
|
||||
public void doRenderShadowAndFire(Entity entity, double x, double y, double z, float yaw, float partial) {
|
||||
Trace.add("shadow " + entity.name);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReportedException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ReportedException(String description, Throwable cause) {
|
||||
super(description, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
|
||||
public class WholeTarget {
|
||||
|
||||
public static boolean guard;
|
||||
|
||||
public int compute(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public void boom() {
|
||||
Trace.add("boom");
|
||||
throw new IllegalStateException("boom");
|
||||
}
|
||||
|
||||
public void alpha() {
|
||||
Trace.add("alpha");
|
||||
}
|
||||
|
||||
public void beta() {
|
||||
Trace.add("beta");
|
||||
}
|
||||
|
||||
public synchronized int lockedInstance(int a) {
|
||||
Trace.add("body " + Thread.holdsLock(this));
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Trace.add("run");
|
||||
exit();
|
||||
}
|
||||
|
||||
private void exit() {
|
||||
Trace.add("target exit");
|
||||
}
|
||||
|
||||
public int priority(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a;
|
||||
}
|
||||
|
||||
public int branching(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public void skippedVoid(int a) {
|
||||
Trace.add("skippedVoid " + a);
|
||||
}
|
||||
|
||||
public int reentrant(int a) {
|
||||
Trace.add("reentrant " + a);
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
public String skipAndReturn(String a) {
|
||||
Trace.add("skipAndReturn " + a);
|
||||
return a + "!";
|
||||
}
|
||||
|
||||
public static int entryCatches(int a) {
|
||||
Trace.add("entryCatches " + a);
|
||||
if (a < 0) {
|
||||
throw new IllegalStateException("negative " + a);
|
||||
}
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public int throwingFinally(int a, int b) {
|
||||
return pick(a, b);
|
||||
}
|
||||
|
||||
private static int pick(int a, int b) {
|
||||
if (a > b) {
|
||||
Trace.add("gt");
|
||||
return a;
|
||||
}
|
||||
if (a < b) {
|
||||
Trace.add("lt");
|
||||
return b;
|
||||
}
|
||||
Trace.add("eq");
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static double wideBody(double a, long b) {
|
||||
Trace.add("body");
|
||||
return a + b;
|
||||
}
|
||||
|
||||
public void throwWide(int a) {
|
||||
thrown(a);
|
||||
}
|
||||
|
||||
public void throwObject(int a) {
|
||||
thrown(a);
|
||||
}
|
||||
|
||||
private static void thrown(int a) {
|
||||
Trace.add("body " + a);
|
||||
throw new IllegalStateException("boom " + a);
|
||||
}
|
||||
|
||||
public void throwingEntry(int a) {
|
||||
Trace.add("body " + a);
|
||||
}
|
||||
|
||||
public int caught(int a) {
|
||||
try {
|
||||
if (a < 0) {
|
||||
throw new IllegalArgumentException("neg");
|
||||
}
|
||||
return a;
|
||||
} catch (IllegalArgumentException e) {
|
||||
Trace.add("caught " + e.getMessage());
|
||||
return -1;
|
||||
} finally {
|
||||
Trace.add("inner-finally");
|
||||
}
|
||||
}
|
||||
|
||||
public int carryThree(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carryUninitialized(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carryGuarded(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carrySplitAtEnd(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carryWideBeforeFrame(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carryArrays(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int reused(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
private static int carried(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public static int risky(int a) {
|
||||
if (a < 0) {
|
||||
throw new IllegalArgumentException("neg");
|
||||
}
|
||||
return a * 3;
|
||||
}
|
||||
|
||||
public static Object asObject(Object value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Callee;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
|
||||
public class WrapTarget {
|
||||
|
||||
private final Callee callee = new Callee();
|
||||
|
||||
public int wrapped(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public int originalOperands(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int shifted(int a) {
|
||||
final int local = a + 1;
|
||||
final String label = "L" + local;
|
||||
return this.callee.twice(local) + label.length();
|
||||
}
|
||||
}
|
||||
+504
@@ -0,0 +1,504 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration.mixin;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Callee;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Entity;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Render;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.Slice;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.function.IntUnaryOperator;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.CALLEE;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.TWICE;
|
||||
|
||||
@Mixin(value = CallTarget.class, remap = false)
|
||||
public class MixinCallTarget {
|
||||
|
||||
private static final String WIDE = CALLEE + "wide(JD)J";
|
||||
private static final String EXPLODE = CALLEE + "explode(I)I";
|
||||
private static final String MAYBE_BOOM = CALLEE + "maybeBoom(I)V";
|
||||
private static final String DO_RENDER = "Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Render;" + "doRender(Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Entity;DDDFF)V";
|
||||
private static final long QUEUED = -1L;
|
||||
|
||||
@Surround(method = "staticTarget", at = @At(value = "INVOKE", target = CALLEE + "twiceStatic(I)I"), id = "staticTarget")
|
||||
private static void surround$enterStaticTarget(int a) {
|
||||
@Surround.Carry int doubled = a * 2;
|
||||
Trace.add("enterStaticTarget " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("staticTarget")
|
||||
private static void surround$exitStaticTarget(int a, @Surround.Carry int doubled) {
|
||||
Trace.add("exitStaticTarget " + a + " " + doubled);
|
||||
}
|
||||
|
||||
@Surround(method = "interfaceCall", at = @At(value = "INVOKE", target = "Ljava/util/function/IntUnaryOperator;applyAsInt(I)I"), id = "interface")
|
||||
private void surround$enterInterface(IntUnaryOperator op, int a) {
|
||||
Trace.add("enterInterface " + a + " " + (op instanceof Callee));
|
||||
}
|
||||
|
||||
@Surround.Finally("interface")
|
||||
private void surround$exitInterface(Object op) {
|
||||
Trace.add("exitInterface " + (op instanceof Callee));
|
||||
}
|
||||
|
||||
@Surround(method = "privateCall", at = @At(value = "INVOKE", target = "Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget;priv(I)I"), id = "private")
|
||||
private void surround$enterPrivate(CallTarget self, int a) {
|
||||
Trace.add("enterPrivate " + (self == (Object) this) + " " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("private")
|
||||
private void surround$exitPrivate(CallTarget self, int a) {
|
||||
Trace.add("exitPrivate " + (self == (Object) this) + " " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "all", at = @At(value = "INVOKE", target = TWICE), id = "all", require = 3)
|
||||
private void surround$enterAll(Callee callee, int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterAll " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("all")
|
||||
private void surround$exitAll(Callee callee, int a, @Surround.Carry long token) {
|
||||
Trace.add("exitAll " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "sliced", at = @At(value = "INVOKE", target = TWICE), slice = @Slice(from = @At(value = "INVOKE", target = CALLEE + "marker()V")), id = "sliced", require = 1, allow = 1)
|
||||
private void surround$enterSliced(Callee callee, int a) {
|
||||
Trace.add("enterSliced " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("sliced")
|
||||
private void surround$exitSliced(Callee callee, int a) {
|
||||
Trace.add("exitSliced " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "throwingEntry", at = @At(value = "INVOKE", target = TWICE), id = "throwingEntry")
|
||||
private void surround$enterThrowingEntry(Callee callee, int a) {
|
||||
Trace.add("enterThrowingEntry " + a);
|
||||
if (a > 0) {
|
||||
throw new IllegalArgumentException("entry");
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Catch("throwingEntry")
|
||||
private void surround$caughtThrowingEntry(Throwable error) {
|
||||
Trace.add("caughtThrowingEntry");
|
||||
}
|
||||
|
||||
@Surround.Finally("throwingEntry")
|
||||
private void surround$exitThrowingEntry() {
|
||||
Trace.add("exitThrowingEntry");
|
||||
}
|
||||
|
||||
@Inject(method = "composed", at = @At(value = "INVOKE", target = TWICE))
|
||||
private void surround$before(int a, CallbackInfoReturnable<Integer> cir) {
|
||||
Trace.add("before " + a);
|
||||
}
|
||||
|
||||
@Inject(method = "composed", at = @At(value = "INVOKE", target = TWICE, shift = At.Shift.AFTER))
|
||||
private void surround$after(int a, CallbackInfoReturnable<Integer> cir) {
|
||||
Trace.add("after " + a);
|
||||
}
|
||||
|
||||
@ModifyArg(method = "composed", at = @At(value = "INVOKE", target = TWICE))
|
||||
private int surround$plusHundred(int a) {
|
||||
Trace.add("modifyArg " + a);
|
||||
return a + 100;
|
||||
}
|
||||
|
||||
@Surround(method = "composed", at = @At(value = "INVOKE", target = TWICE), id = "composed")
|
||||
private void surround$enterComposed(Callee callee, int a) {
|
||||
Trace.add("enterComposed " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("composed")
|
||||
private void surround$exitComposed(Callee callee, int a) {
|
||||
Trace.add("exitComposed " + a);
|
||||
}
|
||||
|
||||
@Redirect(method = "render", at = @At(value = "INVOKE", target = DO_RENDER))
|
||||
private void surround$redirectDoRender(Render render, Entity entity, double x, double y, double z, float yaw, float partial) {
|
||||
Trace.add("redirect " + entity.name);
|
||||
render.doRender(entity, x, y, z, yaw, partial);
|
||||
}
|
||||
|
||||
@Surround(method = "render", at = @At(value = "INVOKE", target = DO_RENDER), id = "render", require = 1)
|
||||
private void surround$enterDoRender(Render render, Entity entity, double x, double y, double z, float yaw, float partial) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterDoRender " + entity.name + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Catch("render")
|
||||
private void surround$caughtDoRender(Throwable error, Render render, Entity entity, @Surround.Carry long token) {
|
||||
Trace.add("caughtDoRender " + entity.name + " " + error.getMessage() + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("render")
|
||||
private void surround$exitDoRender(Render render, Entity entity, @Surround.Carry long token) {
|
||||
Trace.add("exitDoRender " + entity.name + " " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "byName", at = @At(value = "INVOKE", target = TWICE), id = "byName")
|
||||
private void surround$enterByName(@Surround.Local(name = "second") int second) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterByName " + second + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("byName")
|
||||
private void surround$exitByName(Callee callee, int first, @Surround.Local(name = "second") int second, @Surround.Carry long token) {
|
||||
Trace.add("exitByName " + first + " " + second + " " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "wideLocals", at = @At(value = "INVOKE", target = WIDE), id = "wideLocals")
|
||||
private void surround$enterWideLocals(Callee callee, long a, double b, @Surround.Local long big, @Surround.Local double half) {
|
||||
Trace.add("enterWideLocals " + big + " " + half);
|
||||
}
|
||||
|
||||
@Surround.Finally("wideLocals")
|
||||
private void surround$exitWideLocals(@Surround.Local(index = 4) double half, @Surround.Local(index = 2) long big) {
|
||||
Trace.add("exitWideLocals " + half + " " + big);
|
||||
}
|
||||
|
||||
@Surround(method = "valueBelow", at = @At(value = "INVOKE", target = EXPLODE), id = "valueBelow")
|
||||
private void surround$enterValueBelow(Callee callee, int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterValueBelow " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Catch(value = "valueBelow", handle = true)
|
||||
private int surround$handleValueBelow(IllegalStateException error, Callee callee, int a, @Surround.Carry long token) {
|
||||
Trace.add("handleValueBelow " + a + " " + token);
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Surround.Finally("valueBelow")
|
||||
private void surround$exitValueBelow(Callee callee, int a, @Surround.Carry long token) {
|
||||
Trace.add("exitValueBelow " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "valueSkip", at = @At(value = "INVOKE", target = TWICE), id = "valueSkip")
|
||||
private void surround$enterValueSkip(Callee callee, int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterValueSkip " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Skipped("valueSkip")
|
||||
private int surround$skippedValueSkip(Callee callee, int a, @Surround.Carry long token) {
|
||||
Trace.add("skippedValueSkip " + a + " " + token);
|
||||
return a * 100;
|
||||
}
|
||||
|
||||
@Surround.Finally("valueSkip")
|
||||
private void surround$exitValueSkip(@Surround.Carry long token) {
|
||||
Trace.add("exitValueSkip " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "observed", at = @At(value = "INVOKE", target = MAYBE_BOOM), id = "observed")
|
||||
private void surround$enterObserved(Callee callee, int a) {
|
||||
Trace.add("enterObserved " + a);
|
||||
}
|
||||
|
||||
@Surround.Return("observed")
|
||||
private void surround$observe(Callee callee, int a) {
|
||||
Trace.add("observe " + a);
|
||||
}
|
||||
|
||||
@Surround.Catch(value = "observed", handle = true)
|
||||
private void surround$handleObserved(IllegalStateException error, Callee callee, int a) {
|
||||
Trace.add("handleObserved " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("observed")
|
||||
private void surround$exitObserved(Callee callee, int a) {
|
||||
Trace.add("exitObserved " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "returnAndHandle", at = @At(value = "INVOKE", target = EXPLODE), id = "returnAndHandle")
|
||||
private void surround$enterReturnAndHandle(Callee callee, int a) {
|
||||
Trace.add("enterReturnAndHandle " + a);
|
||||
}
|
||||
|
||||
@Surround.Return("returnAndHandle")
|
||||
private int surround$returnReturnAndHandle(int result) {
|
||||
Trace.add("returnReturnAndHandle " + result);
|
||||
return result * 10;
|
||||
}
|
||||
|
||||
@Surround.Catch(value = "returnAndHandle", handle = true)
|
||||
private int surround$handleReturnAndHandle(IllegalStateException error) {
|
||||
Trace.add("handleReturnAndHandle");
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Surround.Finally("returnAndHandle")
|
||||
private void surround$exitReturnAndHandle() {
|
||||
Trace.add("exitReturnAndHandle");
|
||||
}
|
||||
|
||||
@Surround(method = "skipFinallyCatch", at = @At(value = "INVOKE", target = EXPLODE), id = "sfc")
|
||||
private void surround$enterSfc(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a <= 0;
|
||||
Trace.add("enterSfc " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("sfc")
|
||||
private int surround$skippedSfc(Callee callee, int a) {
|
||||
Trace.add("skippedSfc " + a);
|
||||
if (a == 0) {
|
||||
throw new IllegalStateException("zero");
|
||||
}
|
||||
return -a;
|
||||
}
|
||||
|
||||
@Surround.Return("sfc")
|
||||
private int surround$returnSfc(int result, Callee callee, int a) {
|
||||
Trace.add("returnSfc " + result);
|
||||
return result + 1000;
|
||||
}
|
||||
|
||||
@Surround.Catch("sfc")
|
||||
private void surround$caughtSfc(IllegalStateException error, Callee callee, int a) {
|
||||
Trace.add("caughtSfc " + error.getMessage());
|
||||
}
|
||||
|
||||
@Surround.Finally("sfc")
|
||||
private void surround$exitSfc(Callee callee, int a) {
|
||||
Trace.add("exitSfc " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "nestedSkip", at = @At(value = "INVOKE", target = TWICE), id = "nestedInner")
|
||||
private void surround$enterNestedInner(Callee callee, int a) {
|
||||
Trace.add("enterNestedInner " + a);
|
||||
}
|
||||
|
||||
@Surround.Return("nestedInner")
|
||||
private int surround$returnNestedInner(int result) {
|
||||
Trace.add("returnNestedInner " + result);
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
@Surround.Finally("nestedInner")
|
||||
private void surround$exitNestedInner(Callee callee, int a) {
|
||||
Trace.add("exitNestedInner " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "nestedSkip", at = @At(value = "INVOKE", target = TWICE), id = "nestedOuter")
|
||||
private void surround$enterNestedOuter(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterNestedOuter " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("nestedOuter")
|
||||
private int surround$skippedNestedOuter() {
|
||||
Trace.add("skippedNestedOuter");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Surround.Return("nestedOuter")
|
||||
private int surround$returnNestedOuter(int result) {
|
||||
Trace.add("returnNestedOuter " + result);
|
||||
return result * 10;
|
||||
}
|
||||
|
||||
@Surround.Finally("nestedOuter")
|
||||
private void surround$exitNestedOuter(Callee callee, int a) {
|
||||
Trace.add("exitNestedOuter " + a);
|
||||
}
|
||||
|
||||
@Redirect(method = "redirectedSkip", at = @At(value = "INVOKE", target = TWICE))
|
||||
private int surround$redirectSkip(Callee callee, int a) {
|
||||
Trace.add("redirectSkip " + a);
|
||||
return callee.twice(a) + 1;
|
||||
}
|
||||
|
||||
@Surround(method = "redirectedSkip", at = @At(value = "INVOKE", target = TWICE), id = "redirectedSkip")
|
||||
private void surround$enterRedirectedSkip(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterRedirectedSkip " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("redirectedSkip")
|
||||
private int surround$skippedRedirectedSkip(Callee callee, int a) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Surround.Finally("redirectedSkip")
|
||||
private void surround$exitRedirectedSkip(Callee callee, int a) {
|
||||
Trace.add("exitRedirectedSkip " + a);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static long surround$part(int a) {
|
||||
Trace.add("part " + a);
|
||||
return a < 0 ? QUEUED : a;
|
||||
}
|
||||
|
||||
@Surround(method = "helperSkip", at = @At(value = "INVOKE", target = TWICE), id = "helper")
|
||||
private void surround$enterHelper(Callee callee, int a) {
|
||||
@Surround.Carry final long part = surround$part(a);
|
||||
@Surround.Skip final boolean queued = part == QUEUED;
|
||||
Trace.add("enterHelper " + part);
|
||||
}
|
||||
|
||||
@Surround.Skipped("helper")
|
||||
private int surround$skippedHelper(Callee callee, int a, @Surround.Carry long part) {
|
||||
Trace.add("skippedHelper");
|
||||
return (int) part;
|
||||
}
|
||||
|
||||
@Surround.Finally("helper")
|
||||
private void surround$exitHelper(@Surround.Carry long part) {
|
||||
Trace.add("exitHelper " + part);
|
||||
}
|
||||
|
||||
@Surround(method = "wideSkip", at = @At(value = "INVOKE", target = WIDE), id = "wideSkip")
|
||||
private void surround$enterWideSkip(Callee callee, long a, double b) {
|
||||
@Surround.Carry double halved = b / 2;
|
||||
@Surround.Skip int skip = a < 0 ? 1 : 0;
|
||||
Trace.add("enterWideSkip " + a + " " + b);
|
||||
}
|
||||
|
||||
@Surround.Skipped("wideSkip")
|
||||
private long surround$skippedWideSkip(Callee callee, long a, @Surround.Carry double halved) {
|
||||
Trace.add("skippedWideSkip " + a + " " + halved);
|
||||
return a * 10L;
|
||||
}
|
||||
|
||||
@Surround.Return("wideSkip")
|
||||
private long surround$returnWideSkip(long result, Callee callee, long a, double b, @Surround.Carry double halved) {
|
||||
Trace.add("returnWideSkip " + result + " " + halved);
|
||||
return result + 100L;
|
||||
}
|
||||
|
||||
@Surround.Finally("wideSkip")
|
||||
private void surround$exitWideSkip(Callee callee, long a, double b) {
|
||||
Trace.add("exitWideSkip " + a + " " + b);
|
||||
}
|
||||
|
||||
@Surround(method = "entryOnlySkip", at = @At(value = "INVOKE", target = CALLEE + "render(I)V"), id = "entryOnlySkip")
|
||||
private void surround$enterOnlySkip(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a > 0;
|
||||
Trace.add("enterOnlySkip " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "entryOnlyWhole", id = "entryOnlyWhole")
|
||||
private void surround$enterOnlyWhole() {
|
||||
Trace.add("enterOnlyWhole");
|
||||
}
|
||||
|
||||
@Surround(method = "skipBelowRestored", at = @At(value = "INVOKE", target = TWICE), id = "skipBelowRestored")
|
||||
private void surround$enterSkipBelowRestored(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
try {
|
||||
if (a > 100) {
|
||||
throw new IllegalArgumentException("big");
|
||||
}
|
||||
Trace.add("enterSkipBelowRestored " + a);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Trace.add("entryCaughtSkipBelowRestored " + a);
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Skipped("skipBelowRestored")
|
||||
private int surround$skippedSkipBelowRestored(Callee callee, int a) {
|
||||
Trace.add("skippedSkipBelowRestored " + a);
|
||||
return a * 10;
|
||||
}
|
||||
|
||||
@Surround.Finally("skipBelowRestored")
|
||||
private void surround$exitSkipBelowRestored(Callee callee, int a) {
|
||||
Trace.add("exitSkipBelowRestored " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "innerSkipOuterReturn", at = @At(value = "INVOKE", target = TWICE), id = "innerSkip")
|
||||
private void surround$enterInnerSkip(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterInnerSkip " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("innerSkip")
|
||||
private int surround$skippedInnerSkip(Callee callee, int a) {
|
||||
Trace.add("skippedInnerSkip " + a);
|
||||
return a * 100;
|
||||
}
|
||||
|
||||
@Surround.Finally("innerSkip")
|
||||
private void surround$exitInnerSkip(Callee callee, int a) {
|
||||
Trace.add("exitInnerSkip " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "innerSkipOuterReturn", at = @At(value = "INVOKE", target = TWICE), id = "outerReturn")
|
||||
private void surround$enterOuterReturn(Callee callee, int a) {
|
||||
Trace.add("enterOuterReturn " + a);
|
||||
}
|
||||
|
||||
@Surround.Return("outerReturn")
|
||||
private int surround$returnOuterReturn(int result) {
|
||||
Trace.add("returnOuterReturn " + result);
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
@Surround.Finally("outerReturn")
|
||||
private void surround$exitOuterReturn(Callee callee, int a) {
|
||||
Trace.add("exitOuterReturn " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "skipInsideNew", at = @At(value = "INVOKE", target = TWICE), id = "skipInsideNew")
|
||||
private void surround$enterSkipInsideNew(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterSkipInsideNew " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("skipInsideNew")
|
||||
private int surround$skippedSkipInsideNew(Callee callee, int a) {
|
||||
Trace.add("skippedSkipInsideNew " + a);
|
||||
return a * 10;
|
||||
}
|
||||
|
||||
@Surround.Catch("skipInsideNew")
|
||||
private void surround$caughtSkipInsideNew(Throwable error) {
|
||||
Trace.add("caughtSkipInsideNew");
|
||||
}
|
||||
|
||||
@Surround.Finally("skipInsideNew")
|
||||
private void surround$exitSkipInsideNew(Callee callee, int a) {
|
||||
Trace.add("exitSkipInsideNew " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "movedBody", id = "movedWhole")
|
||||
private void surround$enterMovedWhole(int a) {
|
||||
Trace.add("enterMovedWhole " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("movedWhole")
|
||||
private void surround$exitMovedWhole(int a) {
|
||||
Trace.add("exitMovedWhole " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "movedBody", at = @At(value = "INVOKE", target = TWICE), id = "movedCall")
|
||||
private void surround$enterMovedCall(Callee callee, int b, @Surround.Local(argsOnly = true) int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterMovedCall " + b + " " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("movedCall")
|
||||
private int surround$skippedMovedCall(Callee callee, int b, @Surround.Local(argsOnly = true) int a) {
|
||||
Trace.add("skippedMovedCall " + b + " " + a);
|
||||
return b * 10;
|
||||
}
|
||||
|
||||
@Surround.Finally("movedCall")
|
||||
private void surround$exitMovedCall(Callee callee, int b, @Surround.Local(argsOnly = true) int a) {
|
||||
Trace.add("exitMovedCall " + b + " " + a);
|
||||
}
|
||||
}
|
||||
+389
@@ -0,0 +1,389 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration.mixin;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.WholeTarget;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(value = WholeTarget.class, remap = false)
|
||||
public class MixinWholeTarget {
|
||||
|
||||
@Surround(method = "compute(I)I", id = "compute")
|
||||
private void surround$enter(int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enter " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("compute")
|
||||
private void surround$exit(@Surround.Carry long token) {
|
||||
Trace.add("exit " + token);
|
||||
}
|
||||
|
||||
@Inject(method = "compute(I)I", at = @At("HEAD"))
|
||||
private void surround$head(int a, CallbackInfoReturnable<Integer> cir) {
|
||||
Trace.add("head " + a);
|
||||
}
|
||||
|
||||
@Inject(method = "compute(I)I", at = @At("RETURN"))
|
||||
private void surround$tail(int a, CallbackInfoReturnable<Integer> cir) {
|
||||
Trace.add("tail " + cir.getReturnValue());
|
||||
}
|
||||
|
||||
@Surround(method = "boom()V", id = "boom")
|
||||
private void surround$enterBoom() {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterBoom " + token);
|
||||
}
|
||||
|
||||
@Surround.Catch("boom")
|
||||
private void surround$caughtBoomBroad(Throwable e, @Surround.Carry long token) {
|
||||
Trace.add("caughtBroad " + token + " " + e.getMessage());
|
||||
}
|
||||
|
||||
@Surround.Catch("boom")
|
||||
private void surround$caughtBoom(IllegalStateException e, @Surround.Carry long token) {
|
||||
Trace.add("caught " + token + " " + e.getMessage());
|
||||
}
|
||||
|
||||
@Surround.Finally("boom")
|
||||
private void surround$exitBoom(@Surround.Carry long token) {
|
||||
Trace.add("exitBoom " + token);
|
||||
}
|
||||
|
||||
@Inject(method = "boom()V", at = @At("HEAD"))
|
||||
private void surround$boomHead(CallbackInfo ci) {
|
||||
Trace.add("boomHead");
|
||||
}
|
||||
|
||||
@Surround(method = {"alpha()V", "beta()V"}, id = "both")
|
||||
private void surround$enterBoth() {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterBoth " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("both")
|
||||
private void surround$exitBoth(@Surround.Carry long token) {
|
||||
Trace.add("exitBoth " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "lockedInstance(I)I", id = "instance")
|
||||
private void surround$enterInstance(int a) {
|
||||
Trace.add("enterInstance " + Thread.holdsLock(this));
|
||||
}
|
||||
|
||||
@Surround.Finally("instance")
|
||||
private void surround$exitInstance() {
|
||||
Trace.add("exitInstance " + Thread.holdsLock(this));
|
||||
}
|
||||
|
||||
@Surround(method = "run()V", id = "run")
|
||||
private void surround$enterRun() {
|
||||
Trace.add("enter");
|
||||
}
|
||||
|
||||
@Unique
|
||||
@Surround.Finally("run")
|
||||
private void exit() {
|
||||
Trace.add("handler exit");
|
||||
}
|
||||
|
||||
@Surround(method = "priority(I)I", id = "priority")
|
||||
private void surround$enterLow(int a) {
|
||||
Trace.add("enterLow");
|
||||
}
|
||||
|
||||
@Surround.Return("priority")
|
||||
private int surround$returnLow(int result) {
|
||||
Trace.add("returnLow " + result);
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
@Surround.Finally("priority")
|
||||
private void surround$exitLow() {
|
||||
Trace.add("exitLow");
|
||||
}
|
||||
|
||||
@Surround(method = "branching(I)I", id = "branching")
|
||||
private void surround$enterBranching(int a) {
|
||||
@Surround.Carry String sign = a < 0 ? "negative" : "positive";
|
||||
Trace.add("enterBranching " + sign);
|
||||
}
|
||||
|
||||
@Surround.Finally("branching")
|
||||
private void surround$exitBranching(int a, @Surround.Carry String sign) {
|
||||
Trace.add("exitBranching " + sign);
|
||||
}
|
||||
|
||||
@Surround(method = "skippedVoid(I)V", id = "skippedVoid")
|
||||
private void surround$enterSkippedVoid(int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterSkippedVoid " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("skippedVoid")
|
||||
private void surround$exitSkippedVoid() {
|
||||
Trace.add("exitSkippedVoid");
|
||||
}
|
||||
|
||||
@Surround(method = "reentrant(I)I", id = "reentrant")
|
||||
private void surround$enterReentrant(int a) {
|
||||
@Surround.Carry int direct = 0;
|
||||
@Surround.Skip boolean skip = false;
|
||||
if (!WholeTarget.guard) {
|
||||
WholeTarget.guard = true;
|
||||
direct = ((WholeTarget) (Object) this).reentrant(a + 10);
|
||||
WholeTarget.guard = false;
|
||||
skip = true;
|
||||
}
|
||||
Trace.add("enterReentrant " + a + " " + skip);
|
||||
}
|
||||
|
||||
@Surround.Skipped("reentrant")
|
||||
private int surround$skippedReentrant(int a, @Surround.Carry int direct) {
|
||||
return direct * 2;
|
||||
}
|
||||
|
||||
@Surround.Finally("reentrant")
|
||||
private void surround$exitReentrant(int a) {
|
||||
Trace.add("exitReentrant " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "skipAndReturn(Ljava/lang/String;)Ljava/lang/String;", id = "skipAndReturn")
|
||||
private void surround$enterSkipAndReturn(String a) {
|
||||
@Surround.Skip boolean skip = a.isEmpty();
|
||||
Trace.add("enterSkipAndReturn " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("skipAndReturn")
|
||||
private String surround$skippedSkipAndReturn(String a) {
|
||||
Trace.add("skippedSkipAndReturn");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Surround.Return("skipAndReturn")
|
||||
private String surround$returnSkipAndReturn(String result, String a) {
|
||||
Trace.add("returnSkipAndReturn " + result);
|
||||
if (result.equals("x!")) {
|
||||
throw new IllegalStateException("return");
|
||||
}
|
||||
return result + "?";
|
||||
}
|
||||
|
||||
@Surround.Catch("skipAndReturn")
|
||||
private void surround$caughtSkipAndReturn(Throwable e) {
|
||||
Trace.add("caughtSkipAndReturn " + e.getMessage());
|
||||
}
|
||||
|
||||
@Surround.Finally("skipAndReturn")
|
||||
private void surround$exitSkipAndReturn(String a) {
|
||||
Trace.add("exitSkipAndReturn " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "entryCatches(I)I", id = "entryCatches")
|
||||
private static void surround$enterEntryCatches(int a) {
|
||||
@Surround.Carry final boolean direct = !WholeTarget.guard;
|
||||
if (direct) {
|
||||
WholeTarget.guard = true;
|
||||
try {
|
||||
WholeTarget.entryCatches(a);
|
||||
} catch (Throwable t) {
|
||||
WholeTarget.guard = false;
|
||||
Trace.add("entryRethrow " + t.getMessage());
|
||||
throw t;
|
||||
}
|
||||
Trace.add("enterEntryCatches " + a);
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("entryCatches")
|
||||
private static void surround$exitEntryCatches(@Surround.Carry boolean direct) {
|
||||
if (direct) {
|
||||
WholeTarget.guard = false;
|
||||
}
|
||||
Trace.add("exitEntryCatches " + direct);
|
||||
}
|
||||
|
||||
@Surround(method = "wideBody(DJ)D", id = "wideBody")
|
||||
private static void surround$enterWideBody(double a, long b) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enter " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("wideBody")
|
||||
private static void surround$exitWideBody(@Surround.Carry long token) {
|
||||
Trace.add("exit " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "throwingEntry(I)V", id = "throwingEntry")
|
||||
private void surround$enterThrowingEntry(int a) {
|
||||
Trace.add("enterThrows " + a);
|
||||
if (a > 0) {
|
||||
throw new IllegalArgumentException("entry failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("throwingEntry")
|
||||
private void surround$exitThrowingEntry() {
|
||||
Trace.add("exitNothing");
|
||||
}
|
||||
|
||||
@Surround(method = "throwingFinally(II)I", id = "throwingFinally")
|
||||
private void surround$enterThrowingFinally(int a, int b) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enter " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("throwingFinally")
|
||||
private void surround$exitThrowingFinally(@Surround.Carry long token) {
|
||||
Trace.add("exitThrows " + token);
|
||||
throw new UnsupportedOperationException("finally failed");
|
||||
}
|
||||
|
||||
@Surround(method = "caught(I)I", id = "caught")
|
||||
private void surround$enterCaught(int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enter " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("caught")
|
||||
private void surround$exitCaught(@Surround.Carry long token) {
|
||||
Trace.add("exit " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "carryThree(I)I", id = "carryThree")
|
||||
private void surround$enterCarryThree(int a) {
|
||||
@Surround.Carry int count = a + 1;
|
||||
@Surround.Carry long stamp = 100L + a;
|
||||
@Surround.Carry String label = "L" + a;
|
||||
Trace.add("enter " + count + " " + stamp + " " + label);
|
||||
}
|
||||
|
||||
@Surround.Finally("carryThree")
|
||||
private void surround$exitCarryThree(@Surround.Carry String label, @Surround.Carry long stamp, @Surround.Carry int count) {
|
||||
Trace.add("exit " + count + " " + stamp + " " + label);
|
||||
}
|
||||
|
||||
@Surround(method = "carryUninitialized(I)I", id = "carryUninitialized")
|
||||
private void surround$enterCarryUninitialized(int a) {
|
||||
@Surround.Carry String label = new String(a > 0 ? "pos" : "neg");
|
||||
Trace.add("enter " + label);
|
||||
}
|
||||
|
||||
@Surround.Finally("carryUninitialized")
|
||||
private void surround$exitCarryUninitialized(@Surround.Carry String label) {
|
||||
Trace.add("exit " + label);
|
||||
}
|
||||
|
||||
@Surround(method = "carryGuarded(I)I", id = "carryGuarded")
|
||||
private void surround$enterCarryGuarded(int a) {
|
||||
@Surround.Carry int state = 0;
|
||||
try {
|
||||
state = WholeTarget.risky(a);
|
||||
} catch (IllegalArgumentException e) {
|
||||
state = -1;
|
||||
Trace.add("recovered");
|
||||
}
|
||||
Trace.add("enter " + state);
|
||||
}
|
||||
|
||||
@Surround.Finally("carryGuarded")
|
||||
private void surround$exitCarryGuarded(@Surround.Carry int flag) {
|
||||
Trace.add("exit " + flag);
|
||||
}
|
||||
|
||||
@Surround(method = "carrySplitAtEnd(I)I", id = "carrySplitAtEnd")
|
||||
private void surround$enterCarrySplitAtEnd(int a) {
|
||||
@Surround.Carry int flag;
|
||||
if (a > 0) {
|
||||
flag = 1;
|
||||
} else {
|
||||
flag = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("carrySplitAtEnd")
|
||||
private void surround$exitCarrySplitAtEnd(@Surround.Carry int flag) {
|
||||
Trace.add("exit " + flag);
|
||||
}
|
||||
|
||||
@Surround(method = "carryWideBeforeFrame(I)I", id = "carryWideBeforeFrame")
|
||||
private void surround$enterCarryWideBeforeFrame(int a) {
|
||||
@Surround.Carry long stamp = a;
|
||||
@Surround.Carry int count = a + 1;
|
||||
if (a > 0) {
|
||||
Trace.add("positive");
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("carryWideBeforeFrame")
|
||||
private void surround$exitCarryWideBeforeFrame(@Surround.Carry long stamp, @Surround.Carry int count) {
|
||||
Trace.add("exit " + stamp + " " + count);
|
||||
}
|
||||
|
||||
@Surround(method = "carryArrays(I)I", id = "carryArrays")
|
||||
private void surround$enterCarryArrays(int a) {
|
||||
@Surround.Carry("elements") int[] elements = {a};
|
||||
String @Surround.Carry("names") [] names = {"n" + a};
|
||||
}
|
||||
|
||||
@Surround.Finally("carryArrays")
|
||||
private void surround$exitCarryArrays(@Surround.Carry("names") String[] names, @Surround.Carry("elements") int[] elements) {
|
||||
Trace.add("exit " + names[0] + " " + elements[0]);
|
||||
}
|
||||
|
||||
@Surround(method = "reused(I)I", id = "reused")
|
||||
private void surround$enterReused(int a) {
|
||||
{
|
||||
int tmp = a + 5;
|
||||
Trace.add("tmp " + tmp);
|
||||
}
|
||||
@Surround.Carry long prev = a + 7;
|
||||
Trace.add("enter " + prev);
|
||||
}
|
||||
|
||||
@Surround.Finally("reused")
|
||||
private void surround$exitPrev(@Surround.Carry long prev) {
|
||||
Trace.add("exit " + prev);
|
||||
}
|
||||
|
||||
@Surround(method = "throwWide(I)V", id = "throwWide")
|
||||
private void surround$enterThrowWide(int a) {
|
||||
@Surround.Carry long first = a + 1L;
|
||||
String scratch = "s" + a;
|
||||
@Surround.Carry double second = a / 2.0d;
|
||||
@Surround.Carry int third = a;
|
||||
Trace.add("enter " + first + " " + second + " " + third + " " + scratch);
|
||||
}
|
||||
|
||||
@Surround.Catch("throwWide")
|
||||
private void surround$caughtThrowWide(Throwable e, @Surround.Carry long first, @Surround.Carry double second, @Surround.Carry int third) {
|
||||
Trace.add("caught " + first + " " + second + " " + third);
|
||||
}
|
||||
|
||||
@Surround.Finally("throwWide")
|
||||
private void surround$exitThrowWide(@Surround.Carry double second, @Surround.Carry int third, @Surround.Carry long first) {
|
||||
Trace.add("exit " + first + " " + second + " " + third);
|
||||
}
|
||||
|
||||
@Surround(method = "throwObject(I)V", id = "throwObject")
|
||||
private void surround$enterThrowObject(int a) {
|
||||
@Surround.Carry Object prev = WholeTarget.asObject("o" + a);
|
||||
Trace.add("enter " + prev);
|
||||
}
|
||||
|
||||
@Surround.Catch("throwObject")
|
||||
private void surround$caughtThrowObject(IllegalStateException e, @Surround.Carry String prev) {
|
||||
Trace.add("caught " + prev.length() + " " + prev);
|
||||
throw new UnsupportedOperationException("replaced", e);
|
||||
}
|
||||
|
||||
@Surround.Finally("throwObject")
|
||||
private void surround$exitThrowObject(@Surround.Carry String prev) {
|
||||
Trace.add("exit " + prev.length() + " " + prev);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration.mixin;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.WholeTarget;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(value = WholeTarget.class, priority = 1100, remap = false)
|
||||
public class MixinWholeTargetHigh {
|
||||
|
||||
@Surround(method = "priority(I)I")
|
||||
private void surround$enterHigh(int a) {
|
||||
Trace.add("enterHigh");
|
||||
}
|
||||
|
||||
@Surround.Return
|
||||
private int surround$returnHigh(int result) {
|
||||
Trace.add("returnHigh " + result);
|
||||
return result * 10;
|
||||
}
|
||||
|
||||
@Surround.Finally
|
||||
private void surround$exitHigh() {
|
||||
Trace.add("exitHigh");
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration.mixinextras;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Callee;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.WrapTarget;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalIntRef;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.TWICE;
|
||||
|
||||
@Mixin(value = WrapTarget.class, remap = false)
|
||||
public class MixinWrapTarget {
|
||||
|
||||
@Surround(method = "wrapped", id = "wrapped")
|
||||
private void surround$enterWrapped(int a) {
|
||||
Trace.add("entry " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("wrapped")
|
||||
private void surround$exitWrapped() {
|
||||
Trace.add("finally");
|
||||
}
|
||||
|
||||
@WrapMethod(method = "wrapped")
|
||||
private int wrapWrapped(int a, Operation<Integer> original) {
|
||||
Trace.add("wrap-before " + a);
|
||||
final int result = original.call(a);
|
||||
Trace.add("wrap-after " + result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@WrapOperation(method = "originalOperands", at = @At(value = "INVOKE", target = TWICE))
|
||||
private int wrapOperation$shiftArgument(Callee callee, int a, Operation<Integer> original) {
|
||||
Trace.add("wrap " + a);
|
||||
return original.call(callee, a + 100);
|
||||
}
|
||||
|
||||
@Surround(method = "originalOperands", at = @At(value = "INVOKE", target = TWICE), id = "originalOperands")
|
||||
private void surround$enterOriginalOperands(Callee callee, int a) {
|
||||
Trace.add("enter " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("originalOperands")
|
||||
private void surround$exitOriginalOperands(Callee callee, int a) {
|
||||
Trace.add("exit " + a);
|
||||
}
|
||||
|
||||
@WrapMethod(method = "shifted")
|
||||
private int wrapShifted(int a, Operation<Integer> original, @Share("seen") LocalIntRef seen) {
|
||||
seen.set(a);
|
||||
Trace.add("wrap " + a);
|
||||
return original.call(a);
|
||||
}
|
||||
|
||||
@Surround(method = "shifted", at = @At(value = "INVOKE", target = TWICE), id = "shifted")
|
||||
private void surround$enterShifted(Callee callee, int value, @Surround.Local(ordinal = 1) int local, @Surround.Local String label) {
|
||||
Trace.add("enterShifted " + value + " " + local + " " + label);
|
||||
}
|
||||
|
||||
@Surround.Finally("shifted")
|
||||
private void surround$exitShifted(@Surround.Local(name = "local") int local, @Surround.Local String label) {
|
||||
Trace.add("exitShifted " + local + " " + label);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.targets;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
public final class Trace {
|
||||
|
||||
private static final List<String> EVENTS = new ArrayList<>();
|
||||
private static long counter;
|
||||
|
||||
private Trace() {
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
EVENTS.clear();
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
public static void add(String event) {
|
||||
EVENTS.add(event);
|
||||
}
|
||||
|
||||
public static void assertEvents(String... expected) {
|
||||
assertArrayEquals(expected, EVENTS.toArray(new String[EVENTS.size()]), EVENTS.toString());
|
||||
}
|
||||
|
||||
public static long nextToken() {
|
||||
return ++counter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// class version 52.0 (52)
|
||||
// access flags 0x21
|
||||
public class com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget {
|
||||
|
||||
|
||||
// access flags 0x1
|
||||
public valueBelow(I)I
|
||||
TRYCATCHBLOCK L0 L1 L2 java/lang/IllegalStateException
|
||||
TRYCATCHBLOCK L3 L4 L5 null
|
||||
TRYCATCHBLOCK L0 L1 L5 null
|
||||
L6
|
||||
ILOAD 1
|
||||
ALOAD 0
|
||||
GETFIELD com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.callee : Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;
|
||||
ILOAD 1
|
||||
ISTORE 3
|
||||
ASTORE 2
|
||||
ISTORE 4
|
||||
L7
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.nextToken ()J
|
||||
LSTORE 5
|
||||
L8
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.md$jvmdowngrader$concat$surround$enterValueBelow$1$3f (IJ)Ljava/lang/String;
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.add (Ljava/lang/String;)V
|
||||
L9
|
||||
ILOAD 4
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
L0
|
||||
INVOKEVIRTUAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee.explode (I)I
|
||||
L1
|
||||
ALOAD 0
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueBelow (Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)V
|
||||
GOTO L10
|
||||
L2
|
||||
FRAME FULL [com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget I com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee I I J] [java/lang/IllegalStateException]
|
||||
ASTORE 7
|
||||
ILOAD 4
|
||||
L3
|
||||
ALOAD 0
|
||||
ALOAD 7
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$handleValueBelow (Ljava/lang/IllegalStateException;Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)I
|
||||
L4
|
||||
ALOAD 0
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueBelow (Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)V
|
||||
GOTO L10
|
||||
L5
|
||||
FRAME SAME1 java/lang/Throwable
|
||||
ASTORE 7
|
||||
ALOAD 0
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueBelow (Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)V
|
||||
ALOAD 7
|
||||
ATHROW
|
||||
L10
|
||||
FRAME FULL [com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget I com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee I I J] [I I]
|
||||
BIPUSH 10
|
||||
IMUL
|
||||
IADD
|
||||
IRETURN
|
||||
L11
|
||||
LOCALVARIABLE this Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget; L6 L11 0
|
||||
LOCALVARIABLE a I L6 L11 1
|
||||
MAXSTACK = 7
|
||||
MAXLOCALS = 8
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// class version 52.0 (52)
|
||||
// access flags 0x21
|
||||
public class com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget {
|
||||
|
||||
|
||||
// access flags 0x1
|
||||
public valueSkip(I)I
|
||||
TRYCATCHBLOCK L0 L1 L2 null
|
||||
L3
|
||||
ALOAD 0
|
||||
GETFIELD com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.callee : Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;
|
||||
ILOAD 1
|
||||
ISTORE 3
|
||||
ASTORE 2
|
||||
L4
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.nextToken ()J
|
||||
LSTORE 4
|
||||
L5
|
||||
ILOAD 3
|
||||
IFGE L6
|
||||
ICONST_1
|
||||
GOTO L7
|
||||
L6
|
||||
FRAME APPEND [com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee I J]
|
||||
ICONST_0
|
||||
L7
|
||||
FRAME SAME1 I
|
||||
ISTORE 6
|
||||
L8
|
||||
ILOAD 3
|
||||
LLOAD 4
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.md$jvmdowngrader$concat$surround$enterValueSkip$1$50 (IJ)Ljava/lang/String;
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.add (Ljava/lang/String;)V
|
||||
L9
|
||||
ILOAD 6
|
||||
IFEQ L10
|
||||
L0
|
||||
ALOAD 0
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 4
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$skippedValueSkip (Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)I
|
||||
GOTO L1
|
||||
L10
|
||||
FRAME APPEND [I]
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
INVOKEVIRTUAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee.twice (I)I
|
||||
L1
|
||||
FRAME SAME1 I
|
||||
ALOAD 0
|
||||
LLOAD 4
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueSkip (J)V
|
||||
GOTO L11
|
||||
L2
|
||||
FRAME SAME1 java/lang/Throwable
|
||||
ASTORE 7
|
||||
ALOAD 0
|
||||
LLOAD 4
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueSkip (J)V
|
||||
ALOAD 7
|
||||
ATHROW
|
||||
L11
|
||||
FRAME SAME1 I
|
||||
IRETURN
|
||||
L12
|
||||
LOCALVARIABLE this Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget; L3 L12 0
|
||||
LOCALVARIABLE a I L3 L12 1
|
||||
MAXSTACK = 5
|
||||
MAXLOCALS = 8
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// class version 52.0 (52)
|
||||
// access flags 0x21
|
||||
public class com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget {
|
||||
|
||||
|
||||
// access flags 0x1
|
||||
public skipAndReturn(Ljava/lang/String;)Ljava/lang/String;
|
||||
TRYCATCHBLOCK L0 L1 L2 java/lang/Throwable
|
||||
TRYCATCHBLOCK L3 L4 L5 null
|
||||
TRYCATCHBLOCK L1 L6 L5 null
|
||||
TRYCATCHBLOCK L0 L1 L5 null
|
||||
L7
|
||||
ALOAD 1
|
||||
INVOKEVIRTUAL java/lang/String.isEmpty ()Z
|
||||
ISTORE 2
|
||||
L8
|
||||
ALOAD 1
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.md$jvmdowngrader$concat$surround$enterSkipAndReturn$1 (Ljava/lang/String;)Ljava/lang/String;
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.add (Ljava/lang/String;)V
|
||||
L9
|
||||
ILOAD 2
|
||||
IFEQ L10
|
||||
L0
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$skippedSkipAndReturn (Ljava/lang/String;)Ljava/lang/String;
|
||||
GOTO L6
|
||||
L10
|
||||
FRAME APPEND [I]
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.skipAndReturn$surround (Ljava/lang/String;)Ljava/lang/String;
|
||||
L1
|
||||
ASTORE 4
|
||||
ALOAD 0
|
||||
ALOAD 4
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$returnSkipAndReturn (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
|
||||
L6
|
||||
FRAME SAME1 java/lang/String
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$exitSkipAndReturn (Ljava/lang/String;)V
|
||||
GOTO L11
|
||||
L2
|
||||
FRAME SAME1 java/lang/Throwable
|
||||
ASTORE 3
|
||||
L3
|
||||
ALOAD 0
|
||||
ALOAD 3
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$caughtSkipAndReturn (Ljava/lang/Throwable;)V
|
||||
L4
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$exitSkipAndReturn (Ljava/lang/String;)V
|
||||
ALOAD 3
|
||||
ATHROW
|
||||
L5
|
||||
FRAME SAME1 java/lang/Throwable
|
||||
ASTORE 3
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$exitSkipAndReturn (Ljava/lang/String;)V
|
||||
ALOAD 3
|
||||
ATHROW
|
||||
L11
|
||||
FRAME SAME1 java/lang/String
|
||||
ARETURN
|
||||
L12
|
||||
LOCALVARIABLE this Lcom/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget; L7 L12 0
|
||||
LOCALVARIABLE a Ljava/lang/String; L7 L12 1
|
||||
LOCALVARIABLE skip Z L8 L9 2
|
||||
LOCALVARIABLE surround$error Ljava/lang/Throwable; L2 L12 3
|
||||
LOCALVARIABLE surround$result Ljava/lang/String; L1 L6 4
|
||||
MAXSTACK = 3
|
||||
MAXLOCALS = 5
|
||||
|
||||
// access flags 0x2
|
||||
private skipAndReturn$surround(Ljava/lang/String;)Ljava/lang/String;
|
||||
L0
|
||||
ALOAD 1
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.jvmdowngrader$concat$skipAndReturn$1 (Ljava/lang/String;)Ljava/lang/String;
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.add (Ljava/lang/String;)V
|
||||
L1
|
||||
ALOAD 1
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.jvmdowngrader$concat$skipAndReturn$2 (Ljava/lang/String;)Ljava/lang/String;
|
||||
ARETURN
|
||||
L2
|
||||
LOCALVARIABLE this Lcom/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget; L0 L2 0
|
||||
LOCALVARIABLE a Ljava/lang/String; L0 L2 1
|
||||
MAXSTACK = 1
|
||||
MAXLOCALS = 2
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "com.gtnewhorizons.angelica.experimental.surround.integration.mixinextras",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"MixinWrapTarget"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "com.gtnewhorizons.angelica.experimental.surround.integration.mixin",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"MixinCallTarget",
|
||||
"MixinWholeTarget",
|
||||
"MixinWholeTargetHigh"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
extensions.getByType<JavaPluginExtension>().apply {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "GTNH Maven"
|
||||
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
|
||||
}
|
||||
maven {
|
||||
name = "Minecraft Libraries"
|
||||
url = uri("https://libraries.minecraft.net")
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
dependencies {
|
||||
add("shadowImplementation", project(":experimental-surround-runtime")) { isTransitive = false }
|
||||
add("mixinAnnotationProcessor", project(":experimental-surround-ap"))
|
||||
}
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
minimize {
|
||||
exclude(project(":experimental-surround-runtime"))
|
||||
}
|
||||
relocate("com.gtnewhorizons.angelica.experimental.surround", "com.gtnewhorizons.angelica.experimental.surround")
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
include("experimental-surround-runtime", "experimental-surround-ap")
|
||||
project(":experimental-surround-runtime").projectDir = file("runtime")
|
||||
project(":experimental-surround-ap").projectDir = file("ap")
|
||||
@@ -64,6 +64,10 @@ public final class SystemProperties {
|
||||
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
|
||||
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
|
||||
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
|
||||
public static final float FLYBY_PITCH = parseFloat("angelica.flyby.pitch");
|
||||
public static final int FLYBY_SCREENSHOTS = Integer.getInteger("angelica.flyby.screenshots", 0);
|
||||
public static final boolean FLYBY_DEBUG_HUD = Boolean.getBoolean("angelica.flyby.debugHud");
|
||||
public static final String FLYBY_CRASH_TEST = System.getProperty("angelica.flyby.crashTest", "");
|
||||
|
||||
// Debug
|
||||
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
|
||||
@@ -167,6 +171,16 @@ public final class SystemProperties {
|
||||
}
|
||||
}
|
||||
|
||||
private static float parseFloat(String key) {
|
||||
final String raw = System.getProperty(key);
|
||||
if (raw == null || raw.isEmpty()) return Float.NaN;
|
||||
try {
|
||||
return Float.parseFloat(raw);
|
||||
} catch (NumberFormatException e) {
|
||||
return Float.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDeobf() {
|
||||
return Launch.blackboard != null && Boolean.TRUE.equals(Launch.blackboard.get("fml.deobfuscatedEnvironment"));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ lwjgl2 = "2.9.4-nightly-20150209"
|
||||
guava = "18.0"
|
||||
log4j = "2.0-beta9"
|
||||
asmCompile = "5.0.3"
|
||||
launchwrapper = "1.12"
|
||||
# End Frozen
|
||||
|
||||
lwjgl3 = "3.4.2-SNAPSHOT"
|
||||
@@ -15,6 +16,7 @@ eventbus = "7.0-beta.15"
|
||||
glslTransformLib = "0.2.0-33.g3ec381e"
|
||||
antlr4Runtime = "4.13.2"
|
||||
jcpp = "1.4.14"
|
||||
commonsIo = "2.11.0"
|
||||
tracy = "0.14.1"
|
||||
asyncProfiler = "4.4"
|
||||
retroFuturaBootstrap = "1.1.0"
|
||||
@@ -48,6 +50,8 @@ guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
||||
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
|
||||
joml = { module = "org.joml:joml", version.ref = "joml" }
|
||||
commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commonsLang3" }
|
||||
launchwrapper = { module = "net.minecraft:launchwrapper", version.ref = "launchwrapper" }
|
||||
commons-io = { module = "commons-io:commons-io", version.ref = "commonsIo" }
|
||||
junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
|
||||
|
||||
# Versions come from junit-bom via platform()
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Surround smoke scene. ~ is the flight start; ground-standing level is ~-2. Yaw 0 faces south (+z).
|
||||
|
||||
# setup
|
||||
gamerule doMobSpawning false
|
||||
gamerule doFireTick false
|
||||
gamerule mobGriefing false
|
||||
difficulty 1
|
||||
clear @p
|
||||
give @p Thaumcraft:ItemThaumometer 1
|
||||
give @p minecraft:glass 64
|
||||
give @p minecraft:chest 3
|
||||
give @p minecraft:diamond_sword 1 0 {ench:[{id:16s,lvl:1s}]}
|
||||
|
||||
# south: glass wall, stained glass, test ISBRH, torch
|
||||
setblock ~-1 ~-2 ~7 minecraft:glass
|
||||
setblock ~0 ~-2 ~7 minecraft:glass
|
||||
setblock ~1 ~-2 ~7 minecraft:glass
|
||||
setblock ~-1 ~-1 ~7 minecraft:glass
|
||||
setblock ~0 ~-1 ~7 minecraft:glass
|
||||
setblock ~1 ~-1 ~7 minecraft:glass
|
||||
setblock ~-1 ~0 ~7 minecraft:glass
|
||||
setblock ~0 ~0 ~7 minecraft:glass
|
||||
setblock ~1 ~0 ~7 minecraft:glass
|
||||
setblock ~2 ~-2 ~7 minecraft:stained_glass 3
|
||||
setblock ~3 ~-2 ~7 minecraft:stained_glass 14
|
||||
setblock ~-3 ~-2 ~5 angelica:test_isbrh
|
||||
setblock ~-3 ~-1 ~5 angelica:test_isbrh
|
||||
setblock ~3 ~-2 ~5 minecraft:torch
|
||||
setblock ~4 ~-2 ~6 minecraft:leaves
|
||||
setblock ~5 ~-2 ~6 minecraft:leaves
|
||||
setblock ~4 ~-3 ~4 minecraft:water
|
||||
|
||||
# west: chests, ender chest, item frames
|
||||
setblock ~-5 ~-2 ~-1 minecraft:chest
|
||||
setblock ~-5 ~-2 ~0 minecraft:chest
|
||||
setblock ~-5 ~-2 ~2 minecraft:ender_chest
|
||||
setblock ~-6 ~-1 ~-3 minecraft:stone
|
||||
setblock ~-6 ~-1 ~-5 minecraft:stone
|
||||
summon ItemFrame ~-4.7 ~-0.5 ~-2.5 {TileX:$x-6,TileY:$y-1,TileZ:$z-3,Direction:3b,Item:{id:264s,Count:1b},ForgeData:{flyby:1b}}
|
||||
summon ItemFrame ~-4.7 ~-0.5 ~-4.5 {TileX:$x-6,TileY:$y-1,TileZ:$z-5,Direction:3b,Item:{id:358s,Count:1b,Damage:0s},ForgeData:{flyby:1b}}
|
||||
|
||||
# east: mobs and dropped items
|
||||
summon Spider ~6 ~-2 ~-3 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
|
||||
summon Enderman ~6 ~-2 ~0 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
|
||||
summon Zombie ~6 ~-2 ~3 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
|
||||
summon Item ~4 ~-2 ~-1 {Item:{id:20s,Count:1b},Age:-32768s,ForgeData:{flyby:1b}}
|
||||
summon Item ~4 ~-2 ~1 {Item:{id:264s,Count:1b},Age:-32768s,ForgeData:{flyby:1b}}
|
||||
summon Item ~4 ~-2 ~3 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,ForgeData:{flyby:1b}}
|
||||
|
||||
# north: falling sand, dying dragon
|
||||
summon FallingSand ~0 ~150 ~-6 {TileID:12,Time:1b,ForgeData:{flyby:1b}}
|
||||
summon FallingSand ~1 ~200 ~-6 {TileID:12,Time:1b,ForgeData:{flyby:1b}}
|
||||
summon FallingSand ~-1 ~240 ~-6 {TileID:12,Time:1b,ForgeData:{flyby:1b}}
|
||||
summon EnderDragon ~0 ~6 ~-24 {HealF:0.0f,ForgeData:{flyby:1b}}
|
||||
Executable
+186
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env python3
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
MIN_SHOT_BYTES = 20000
|
||||
SHOTS_REQUIRED = 8
|
||||
CRASH_MARK = "angelica isbrh crashtest"
|
||||
CATCH_MARK = "Caught an exception during ISBRH rendering"
|
||||
|
||||
BAD_LINE = re.compile(
|
||||
r"InvalidInjectionException|InjectionError|MixinApplyError|MixinTransformerError|"
|
||||
r"InvalidMixinException|MixinPrepareError|skipped: the surrounded call was removed|"
|
||||
r"flyby scene command did not execute"
|
||||
)
|
||||
ANGELICA_FRAME = re.compile(
|
||||
r"com\.gtnewhorizons\.angelica|net\.coderbot|org\.embeddedt|org\.taumc|com\.prupe|"
|
||||
r"jss\.notfine|\$surround|angelica\$"
|
||||
)
|
||||
FRAMES_RE = re.compile(r"Flyby \S+: (\d+) frames")
|
||||
|
||||
|
||||
def split_traces(lines):
|
||||
traces = []
|
||||
cur = None
|
||||
for i, line in enumerate(lines):
|
||||
s = line.strip()
|
||||
if s.startswith("at ") and line[:1] in ("\t", " "):
|
||||
if cur is None:
|
||||
cur = [lines[i - 1] if i > 0 else ""]
|
||||
cur.append(line)
|
||||
elif cur is not None and (s.startswith("Caused by:") or s.startswith("... ") or s.startswith("Suppressed:")):
|
||||
cur.append(line)
|
||||
else:
|
||||
if cur is not None:
|
||||
traces.append(cur)
|
||||
cur = None
|
||||
if cur is not None:
|
||||
traces.append(cur)
|
||||
return traces
|
||||
|
||||
|
||||
def check(variant_dir, exit_status, new_crashes):
|
||||
variant = os.path.basename(os.path.normpath(variant_dir))
|
||||
reasons = []
|
||||
details = []
|
||||
|
||||
if str(exit_status) != "0":
|
||||
reasons.append("exit status " + str(exit_status))
|
||||
if new_crashes > 0:
|
||||
reasons.append(str(new_crashes) + " new crash report(s)")
|
||||
|
||||
log_path = os.path.join(variant_dir, "fml-client-latest.log")
|
||||
lines = []
|
||||
if os.path.isfile(log_path):
|
||||
with open(log_path, errors="replace") as f:
|
||||
lines = f.read().splitlines()
|
||||
else:
|
||||
reasons.append("fml-client-latest.log missing")
|
||||
|
||||
if lines:
|
||||
if not any("Flyby complete, shutting down." in l for l in lines):
|
||||
reasons.append("missing 'Flyby complete, shutting down.'")
|
||||
frames = [int(m.group(1)) for m in (FRAMES_RE.search(l) for l in lines) if m]
|
||||
if not frames:
|
||||
reasons.append("missing flyby frames line")
|
||||
elif max(frames) == 0:
|
||||
reasons.append("flyby frames is 0")
|
||||
|
||||
bad = [l for l in lines if BAD_LINE.search(l)]
|
||||
if bad:
|
||||
reasons.append(str(len(bad)) + " mixin/injection/scene failure line(s)")
|
||||
details.extend([" " + l.strip()[:200] for l in bad[:3]])
|
||||
|
||||
offending = []
|
||||
for tr in split_traces(lines):
|
||||
text = "\n".join(tr)
|
||||
if CRASH_MARK in text:
|
||||
continue
|
||||
if any(ANGELICA_FRAME.search(l) for l in tr[1:] if l.strip().startswith("at ")):
|
||||
offending.append(tr)
|
||||
if offending:
|
||||
reasons.append(str(len(offending)) + " Angelica-attributed stack trace(s)")
|
||||
for tr in offending[:3]:
|
||||
details.append(" trace:")
|
||||
details.extend(" " + l.strip()[:200] for l in tr[:3])
|
||||
|
||||
caught = False
|
||||
for i, l in enumerate(lines):
|
||||
if CATCH_MARK in l and any(CRASH_MARK in x for x in lines[i:i + 2]):
|
||||
caught = True
|
||||
break
|
||||
if not caught:
|
||||
reasons.append("no ISBRH catch line for crashtest")
|
||||
|
||||
shots = sorted(glob.glob(os.path.join(variant_dir, "flyby-shot-*.png")))
|
||||
if len(shots) < SHOTS_REQUIRED:
|
||||
reasons.append("only " + str(len(shots)) + " of " + str(SHOTS_REQUIRED) + " screenshots")
|
||||
small = [os.path.basename(p) for p in shots if os.path.getsize(p) < MIN_SHOT_BYTES]
|
||||
if small:
|
||||
reasons.append("small screenshot(s): " + ", ".join(small))
|
||||
|
||||
if reasons:
|
||||
print("FAIL " + variant + ": " + "; ".join(reasons))
|
||||
for d in details:
|
||||
print(d)
|
||||
return 1
|
||||
print("PASS " + variant)
|
||||
return 0
|
||||
|
||||
|
||||
GOOD_LOG = [
|
||||
"[10:00:00] [Client thread/INFO]: Flyby scene done",
|
||||
"[10:00:01] [Client thread/INFO]: Flyby pan: 1234 frames",
|
||||
"[10:00:02] [Client thread/INFO]: Flyby complete, shutting down.",
|
||||
"[10:00:03] [Worker-Main-1/WARN]: Caught an exception during ISBRH rendering for block test_isbrh",
|
||||
"java.lang.IllegalStateException: angelica isbrh crashtest",
|
||||
"\tat com.gtnewhorizons.angelica.rendering.IsbrhTestRenderer.renderWorldBlock(IsbrhTestRenderer.java:20)",
|
||||
"[10:00:04] [Client thread/ERROR]: Botania noise",
|
||||
"java.lang.NullPointerException: boot",
|
||||
"\tat vazkii.botania.Foo.bar(Foo.java:1)",
|
||||
]
|
||||
|
||||
|
||||
def make_variant(root, name, log_lines, shots=8, shot_size=30000):
|
||||
d = os.path.join(root, name)
|
||||
os.makedirs(d)
|
||||
with open(os.path.join(d, "fml-client-latest.log"), "w") as f:
|
||||
f.write("\n".join(log_lines) + "\n")
|
||||
for i in range(shots):
|
||||
with open(os.path.join(d, "flyby-shot-" + str(i) + ".png"), "wb") as f:
|
||||
f.write(b"\0" * shot_size)
|
||||
return d
|
||||
|
||||
|
||||
def self_test():
|
||||
root = tempfile.mkdtemp()
|
||||
failures = []
|
||||
try:
|
||||
bad_trace = GOOD_LOG + [
|
||||
"[10:00:05] [Client thread/ERROR]: oops",
|
||||
"java.lang.RuntimeException: boom",
|
||||
"\tat net.minecraft.client.Foo.bar(Foo.java:1)",
|
||||
"\tat com.gtnewhorizons.angelica.mixins.Baz.qux(Baz.java:2)",
|
||||
]
|
||||
cases = [
|
||||
("clean", GOOD_LOG, {}, 0),
|
||||
("angelica-trace", bad_trace, {}, 1),
|
||||
("crashtest-allowed", GOOD_LOG, {}, 0),
|
||||
("no-isbrh", [l for l in GOOD_LOG if CATCH_MARK not in l], {}, 1),
|
||||
("mixin-error", GOOD_LOG + ["[10:00:05] [main/ERROR]: InvalidInjectionException: bad"], {}, 1),
|
||||
("small-shot", GOOD_LOG, {"shot_size": 100}, 1),
|
||||
]
|
||||
for name, log, kw, want in cases:
|
||||
d = make_variant(root, name, log, **kw)
|
||||
got = check(d, 0, 0)
|
||||
if got != want:
|
||||
failures.append(name)
|
||||
d = make_variant(root, "bad-exit", GOOD_LOG)
|
||||
if check(d, "timeout", 1) != 1:
|
||||
failures.append("bad-exit")
|
||||
finally:
|
||||
shutil.rmtree(root, ignore_errors=True)
|
||||
if failures:
|
||||
print("SELF-TEST FAILED: " + ", ".join(failures))
|
||||
return 1
|
||||
print("SELF-TEST OK")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) == 2 and argv[1] == "--self-test":
|
||||
return self_test()
|
||||
if len(argv) != 4:
|
||||
print("usage: flyby-smoke-check.py <variant_dir> <exit_status|timeout> <new_crash_count>", file=sys.stderr)
|
||||
print(" flyby-smoke-check.py --self-test", file=sys.stderr)
|
||||
return 2
|
||||
return check(argv[1], argv[2], int(argv[3]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
Executable
+201
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env bash
|
||||
# Unattended flyby smoke test: scripted scene, 8 screenshots, log checks
|
||||
#
|
||||
# Usage: scripts/flyby-smoke.sh <name> [ffp|pack|vao]...
|
||||
# Default runs ffp, pack and vao, SDL-GPU backend. vao = ffp with quality.use_celeritas_smooth_lighting=false.
|
||||
# Output: tmp/smoke/<name>/<variant>/ (console.log, fml-client-latest.log, latest.log, flyby-shot-*.png)
|
||||
# Prints one PASS/FAIL line per variant; exits non-zero if any fails.
|
||||
#
|
||||
# QUICKPLAY_WORLD - save to load (default: Flat World - No Mods).
|
||||
# FLYBY_ORIGIN - x,z[,yaw] flight start (default: 1509.5,-924.5,0).
|
||||
# SMOKE_TIMEOUT - seconds before the client is killed (default: 600).
|
||||
# SMOKE_PACK - shaderpack name for the pack variant (default: ComplementaryUnbound_r5.7.1 + EuphoriaPatches_1.8.1).
|
||||
#
|
||||
# Runs from this script's own checkout. Restores run/client/config/shaders.properties and angelica-options.json on exit.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
NAME="${1:?usage: flyby-smoke.sh <name> [ffp|pack|vao]...}"
|
||||
shift
|
||||
if [ $# -eq 0 ]; then
|
||||
set -- ffp pack vao
|
||||
fi
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
QUICKPLAY_WORLD="${QUICKPLAY_WORLD:-Flat World - No Mods}"
|
||||
FLYBY_ORIGIN="${FLYBY_ORIGIN:-1509.5,-924.5,0}"
|
||||
SMOKE_TIMEOUT="${SMOKE_TIMEOUT:-600}"
|
||||
SMOKE_PACK="${SMOKE_PACK:-ComplementaryUnbound_r5.7.1 + EuphoriaPatches_1.8.1}"
|
||||
|
||||
SCENE="$ROOT/scripts/flyby-scenes/surround-smoke.txt"
|
||||
RUN="$ROOT/run/client"
|
||||
SHADERS="$RUN/config/shaders.properties"
|
||||
OUT="$ROOT/tmp/smoke/$NAME"
|
||||
BACKUP="$OUT/shaders.properties.bak"
|
||||
OPTIONS="$RUN/config/angelica-options.json"
|
||||
OPTIONS_BACKUP="$OUT/angelica-options.json.bak"
|
||||
|
||||
for v in "$@"; do
|
||||
case "$v" in
|
||||
ffp|pack|vao) ;;
|
||||
*) echo "error: variant must be ffp, pack or vao, got: $v" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
if [ ! -f "$SCENE" ]; then
|
||||
echo "error: scene not found: $SCENE" >&2
|
||||
exit 1
|
||||
fi
|
||||
for v in "$@"; do
|
||||
if [ "$v" = "pack" ] && [ ! -d "$RUN/shaderpacks/$SMOKE_PACK" ]; then
|
||||
echo "error: shaderpack missing: $RUN/shaderpacks/$SMOKE_PACK" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p "$OUT"
|
||||
python3 - "$SHADERS" "$BACKUP" <<'PY'
|
||||
import shutil, sys
|
||||
shutil.copyfile(sys.argv[1], sys.argv[2])
|
||||
PY
|
||||
python3 - "$OPTIONS" "$OPTIONS_BACKUP" <<'PY'
|
||||
import shutil, sys
|
||||
shutil.copyfile(sys.argv[1], sys.argv[2])
|
||||
PY
|
||||
|
||||
restore_options() {
|
||||
python3 - "$OPTIONS_BACKUP" "$OPTIONS" <<'PY' || true
|
||||
import shutil, sys
|
||||
shutil.copyfile(sys.argv[1], sys.argv[2])
|
||||
PY
|
||||
}
|
||||
|
||||
restore() {
|
||||
python3 - "$BACKUP" "$SHADERS" <<'PY' || true
|
||||
import shutil, sys
|
||||
shutil.copyfile(sys.argv[1], sys.argv[2])
|
||||
PY
|
||||
restore_options
|
||||
}
|
||||
trap restore EXIT
|
||||
|
||||
set_shaders() {
|
||||
python3 - "$SHADERS" "$1" "$2" <<'PY'
|
||||
import re, sys
|
||||
path, enable, pack = sys.argv[1:4]
|
||||
text = open(path).read()
|
||||
def put(text, key, value):
|
||||
pat = re.compile(r'^' + key + r'=.*$', re.M)
|
||||
if pat.search(text):
|
||||
return pat.sub(lambda m: key + '=' + value, text)
|
||||
if text and not text.endswith('\n'):
|
||||
text += '\n'
|
||||
return text + key + '=' + value + '\n'
|
||||
text = put(text, 'enableShaders', enable)
|
||||
if pack:
|
||||
text = put(text, 'shaderPack', pack)
|
||||
open(path, 'w').write(text)
|
||||
PY
|
||||
}
|
||||
|
||||
set_vao() {
|
||||
python3 - "$OPTIONS" <<'PY'
|
||||
import json, sys
|
||||
path = sys.argv[1]
|
||||
with open(path) as f:
|
||||
data = json.load(f)
|
||||
data["quality"]["use_celeritas_smooth_lighting"] = False
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
f.write("\n")
|
||||
PY
|
||||
}
|
||||
|
||||
crash_list() {
|
||||
if [ -d "$RUN/crash-reports" ]; then
|
||||
ls "$RUN/crash-reports" | sort
|
||||
fi
|
||||
}
|
||||
|
||||
FAILED=0
|
||||
for VARIANT in "$@"; do
|
||||
VDIR="$OUT/$VARIANT"
|
||||
python3 - "$VDIR" <<'PY'
|
||||
import shutil, sys
|
||||
shutil.rmtree(sys.argv[1], ignore_errors=True)
|
||||
PY
|
||||
mkdir -p "$VDIR"
|
||||
|
||||
restore_options
|
||||
if [ "$VARIANT" = "vao" ]; then
|
||||
set_vao
|
||||
fi
|
||||
|
||||
if [ "$VARIANT" = "pack" ]; then
|
||||
set_shaders true "$SMOKE_PACK"
|
||||
else
|
||||
set_shaders false ""
|
||||
fi
|
||||
|
||||
mkdir -p "$RUN/screenshots"
|
||||
rm -f "$RUN"/screenshots/flyby-shot-*.png
|
||||
crash_list >| "$VDIR/crashes.before"
|
||||
|
||||
echo "==> $NAME/$VARIANT: launching (timeout ${SMOKE_TIMEOUT}s)"
|
||||
./gradlew runClient25 --console=plain \
|
||||
-Dangelica.flyby.route=pan \
|
||||
-Dangelica.flyby.length=360 \
|
||||
-Dangelica.flyby.warmupTicks=100 \
|
||||
-Dangelica.flyby.exitWhenDone=true \
|
||||
-Dangelica.flyby.commands="$SCENE" \
|
||||
-Dangelica.flyby.origin="$FLYBY_ORIGIN" \
|
||||
-Dangelica.flyby.pitch=15 \
|
||||
-Dangelica.flyby.screenshots=8 \
|
||||
-Dangelica.flyby.debugHud=true \
|
||||
-Dangelica.flyby.crashTest=isbrh \
|
||||
-Dangelica.debug.testBlocks=true \
|
||||
-DquickPlaySingleplayer="$QUICKPLAY_WORLD" \
|
||||
>| "$VDIR/console.log" 2>&1 &
|
||||
PID=$!
|
||||
|
||||
DEADLINE=$(( $(date +%s) + SMOKE_TIMEOUT ))
|
||||
STATUS=""
|
||||
while kill -0 "$PID" 2>/dev/null; do
|
||||
if [ "$(date +%s)" -ge "$DEADLINE" ]; then
|
||||
echo "==> $NAME/$VARIANT: timeout, killing client"
|
||||
pkill -f GradleStart || true
|
||||
kill "$PID" 2>/dev/null || true
|
||||
sleep 2
|
||||
kill -9 "$PID" 2>/dev/null || true
|
||||
STATUS="timeout"
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
RC=0
|
||||
wait "$PID" 2>/dev/null || RC=$?
|
||||
if [ -z "$STATUS" ]; then
|
||||
STATUS="$RC"
|
||||
fi
|
||||
|
||||
crash_list >| "$VDIR/crashes.after"
|
||||
NEW_CRASHES="$(comm -13 "$VDIR/crashes.before" "$VDIR/crashes.after" | wc -l | tr -d ' ')"
|
||||
|
||||
for f in fml-client-latest.log latest.log; do
|
||||
if [ -f "$RUN/logs/$f" ]; then
|
||||
cp -f "$RUN/logs/$f" "$VDIR/$f"
|
||||
fi
|
||||
done
|
||||
for f in "$RUN"/screenshots/flyby-shot-*.png; do
|
||||
if [ -f "$f" ]; then
|
||||
cp -f "$f" "$VDIR/"
|
||||
fi
|
||||
done
|
||||
|
||||
if ! python3 "$ROOT/scripts/flyby-smoke-check.py" "$VDIR" "$STATUS" "$NEW_CRASHES"; then
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
|
||||
exit "$FAILED"
|
||||
@@ -20,6 +20,7 @@ plugins {
|
||||
}
|
||||
|
||||
apply(from = file("gradle/lwjgl-natives.settings.gradle.kts"))
|
||||
apply(from = file("experimental-surround/surround.settings.gradle.kts"))
|
||||
|
||||
include("glsm")
|
||||
include("lwjgl3-backend")
|
||||
|
||||
@@ -7,7 +7,11 @@ import net.minecraft.block.Block;
|
||||
* on multiple threads.
|
||||
*/
|
||||
public class ThreadedBlockData {
|
||||
public static final Thread MAIN_THREAD = Thread.currentThread();
|
||||
public static Thread serverThread;
|
||||
|
||||
public double minX, minY, minZ, maxX, maxY, maxZ;
|
||||
public final Thread owner = Thread.currentThread();
|
||||
|
||||
public ThreadedBlockData() {}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder;
|
||||
import com.gtnewhorizons.angelica.debug.profiling.TracyCaptureNotifier;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.TracyBackend;
|
||||
import com.gtnewhorizons.angelica.rendering.IsbrhTestRenderer;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderRecovery;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
@@ -42,7 +43,7 @@ public class AngelicaCommand extends CommandBase {
|
||||
help.put("flyby", helpLine("flyby <" + FlybyRoute.ids() + ">", "Run a deterministic benchmark route"));
|
||||
help.put("profile", helpLine("profile <start|stop|status>", "Control async-profiler (JFR) recording"));
|
||||
if (SystemProperties.isDeobf()) {
|
||||
help.put("crashtest", helpLine("crashtest", "Arm a crash on the next tile entity render"));
|
||||
help.put("crashtest", helpLine("crashtest [isbrh]", "Arm a crash on the next tile entity or test_isbrh render"));
|
||||
}
|
||||
}
|
||||
if (Tracy.ENABLED) {
|
||||
@@ -109,6 +110,9 @@ public class AngelicaCommand extends CommandBase {
|
||||
if (args.length == 2 && "profile".equalsIgnoreCase(args[0])) {
|
||||
return getListOfStringsMatchingLastWord(args, "start", "stop", "status");
|
||||
}
|
||||
if (args.length == 2 && "crashtest".equalsIgnoreCase(args[0]) && HELP.containsKey("crashtest")) {
|
||||
return getListOfStringsMatchingLastWord(args, "isbrh");
|
||||
}
|
||||
if (args.length == 2 && "tracy".equalsIgnoreCase(args[0])) {
|
||||
return getListOfStringsMatchingLastWord(args, "start", "stop", "status");
|
||||
}
|
||||
@@ -139,7 +143,7 @@ public class AngelicaCommand extends CommandBase {
|
||||
case "flyby" -> handleFlyby(sender, args);
|
||||
case "profile" -> handleProfile(sender, args);
|
||||
case "tracy" -> handleTracy(sender, args);
|
||||
case "crashtest" -> handleCrashTest(sender);
|
||||
case "crashtest" -> handleCrashTest(sender, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +167,12 @@ public class AngelicaCommand extends CommandBase {
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Chunk debug minimap: " + state));
|
||||
}
|
||||
|
||||
private void handleCrashTest(ICommandSender sender) {
|
||||
private void handleCrashTest(ICommandSender sender, String[] args) {
|
||||
if (args.length >= 2 && "isbrh".equalsIgnoreCase(args[1])) {
|
||||
IsbrhTestRenderer.armCrashTest();
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Crash test armed: fires on the next test_isbrh render"));
|
||||
return;
|
||||
}
|
||||
RenderRecovery.armCrashTest();
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Crash test armed: fires on the next tile entity render"));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.gtnewhorizons.angelica.common;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
|
||||
public class BlockIsbrhTest extends Block {
|
||||
|
||||
public static int renderId = -1;
|
||||
|
||||
public BlockIsbrhTest() {
|
||||
super(Material.rock);
|
||||
setHardness(1.5f);
|
||||
setBlockName("angelica.test_isbrh");
|
||||
setBlockTextureName("stone");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRenderType() {
|
||||
return renderId;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.rendering.FpsReducer;
|
||||
import com.gtnewhorizons.angelica.rendering.FramePacer;
|
||||
import com.gtnewhorizons.angelica.rendering.IsbrhTestRenderer;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -20,7 +21,9 @@ import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.ChunkCoordinates;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.util.ScreenShotHelper;
|
||||
import net.minecraft.world.MinecraftException;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.WorldServer;
|
||||
@@ -74,12 +77,18 @@ public final class FlybyRunner {
|
||||
private boolean pauseOnLostFocusSaved;
|
||||
private boolean pauseOnLostFocusOverridden;
|
||||
private boolean vsyncOverridden;
|
||||
private boolean showDebugInfoSaved;
|
||||
private boolean showDebugInfoOverridden;
|
||||
private int shotEvery;
|
||||
private int nextShot;
|
||||
private int shot;
|
||||
|
||||
private static volatile boolean sceneGuarded;
|
||||
private static volatile boolean worldChangesDiscarded;
|
||||
|
||||
private double parkedX, parkedY, parkedZ;
|
||||
private float parkedYaw, parkedPitch;
|
||||
private float flightPitch;
|
||||
private double originX, originZ;
|
||||
private float originYaw;
|
||||
private FlybyOrigin fixedOrigin;
|
||||
@@ -291,6 +300,14 @@ public final class FlybyRunner {
|
||||
}
|
||||
}
|
||||
this.lastFrameNs = now;
|
||||
|
||||
if (this.shot < SystemProperties.FLYBY_SCREENSHOTS && this.tick >= this.nextShot) {
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
final IChatComponent result = ScreenShotHelper.saveScreenshot(mc.mcDataDir, "flyby-shot-" + this.shot + ".png", mc.displayWidth, mc.displayHeight, mc.getFramebuffer());
|
||||
LOGGER.info("Flyby screenshot {}", result.getUnformattedText());
|
||||
this.shot++;
|
||||
this.nextShot += this.shotEvery;
|
||||
}
|
||||
}
|
||||
|
||||
private void overridePauseOnLostFocus(Minecraft mc) {
|
||||
@@ -311,6 +328,7 @@ public final class FlybyRunner {
|
||||
this.parkedZ = player.posZ;
|
||||
this.parkedYaw = player.rotationYaw;
|
||||
this.parkedPitch = player.rotationPitch;
|
||||
this.flightPitch = Float.isNaN(SystemProperties.FLYBY_PITCH) ? this.parkedPitch : SystemProperties.FLYBY_PITCH;
|
||||
this.eyeOffset = player.yOffset;
|
||||
|
||||
final boolean creative = player.capabilities.isCreativeMode;
|
||||
@@ -333,6 +351,14 @@ public final class FlybyRunner {
|
||||
|
||||
this.buildPath();
|
||||
this.suppressPacing();
|
||||
if (SystemProperties.FLYBY_DEBUG_HUD && !this.showDebugInfoOverridden) {
|
||||
this.showDebugInfoSaved = mc.gameSettings.showDebugInfo;
|
||||
this.showDebugInfoOverridden = true;
|
||||
mc.gameSettings.showDebugInfo = true;
|
||||
}
|
||||
if ("isbrh".equals(SystemProperties.FLYBY_CRASH_TEST)) {
|
||||
IsbrhTestRenderer.armCrashTest();
|
||||
}
|
||||
|
||||
this.tick = 0;
|
||||
if (mc.getIntegratedServer() != null) {
|
||||
@@ -472,6 +498,9 @@ public final class FlybyRunner {
|
||||
this.phaseCount = 0;
|
||||
this.lastFrameNs = 0L;
|
||||
this.runStartNs = System.nanoTime();
|
||||
this.shot = 0;
|
||||
this.shotEvery = SystemProperties.FLYBY_SCREENSHOTS > 0 ? this.runTicks / SystemProperties.FLYBY_SCREENSHOTS : 0;
|
||||
this.nextShot = this.shotEvery / 2;
|
||||
FramePacer.beginStats();
|
||||
|
||||
final String config = "backend=" + GLStateManager.getRenderBackendName() + " pacing=" + SystemProperties.FLYBY_PACING + " vsync=" + GLStateManager.getEffectiveVSyncMode() + " discard=" + (mc.getIntegratedServer() != null);
|
||||
@@ -495,7 +524,7 @@ public final class FlybyRunner {
|
||||
player.prevPosZ = player.lastTickPosZ = this.pathZ[last];
|
||||
player.prevRotationYaw = player.rotationYaw = this.pathYaw[last];
|
||||
player.prevRotationYawHead = player.rotationYawHead = this.pathYaw[last];
|
||||
player.prevRotationPitch = player.rotationPitch = this.parkedPitch;
|
||||
player.prevRotationPitch = player.rotationPitch = this.flightPitch;
|
||||
player.setPosition(this.pathX[last], this.flightY, this.pathZ[last]);
|
||||
}
|
||||
|
||||
@@ -523,7 +552,7 @@ public final class FlybyRunner {
|
||||
player.rotationYaw = yaw;
|
||||
player.prevRotationYawHead = prevYaw;
|
||||
player.rotationYawHead = yaw;
|
||||
player.prevRotationPitch = player.rotationPitch = this.parkedPitch;
|
||||
player.prevRotationPitch = player.rotationPitch = this.flightPitch;
|
||||
|
||||
player.setPosition(x, this.flightY, z);
|
||||
}
|
||||
@@ -651,8 +680,10 @@ public final class FlybyRunner {
|
||||
|
||||
this.clearScene(server);
|
||||
|
||||
final FlybyCommandSender sender = new FlybyCommandSender(player, new ChunkCoordinates(MathHelper.floor_double(request.originX()), MathHelper.floor_double(feetY + 0.5D), MathHelper.floor_double(request.originZ())));
|
||||
for (String line : this.sceneCommands) {
|
||||
final ChunkCoordinates anchor = new ChunkCoordinates(MathHelper.floor_double(request.originX()), MathHelper.floor_double(feetY + 0.5D), MathHelper.floor_double(request.originZ()));
|
||||
final FlybyCommandSender sender = new FlybyCommandSender(player, anchor);
|
||||
for (String raw : this.sceneCommands) {
|
||||
final String line = FlybyScene.expand(raw, anchor.posX, anchor.posY, anchor.posZ);
|
||||
if (server.getCommandManager().executeCommand(sender, line) == 0) {
|
||||
LOGGER.warn("flyby scene command did not execute: {}", line);
|
||||
}
|
||||
@@ -723,6 +754,7 @@ public final class FlybyRunner {
|
||||
|
||||
this.returnToOrigin(player);
|
||||
this.restorePauseOnLostFocus(mc);
|
||||
this.restoreShowDebugInfo(mc);
|
||||
this.armed = false;
|
||||
sceneGuarded = false;
|
||||
this.sceneClearRequested = this.sceneCommands.length > 0;
|
||||
@@ -749,6 +781,12 @@ public final class FlybyRunner {
|
||||
this.pauseOnLostFocusOverridden = false;
|
||||
}
|
||||
|
||||
private void restoreShowDebugInfo(Minecraft mc) {
|
||||
if (!this.showDebugInfoOverridden) return;
|
||||
mc.gameSettings.showDebugInfo = this.showDebugInfoSaved;
|
||||
this.showDebugInfoOverridden = false;
|
||||
}
|
||||
|
||||
private void returnToOrigin(EntityClientPlayerMP player) {
|
||||
player.motionX = 0.0D;
|
||||
player.motionY = 0.0D;
|
||||
@@ -807,6 +845,7 @@ public final class FlybyRunner {
|
||||
FramePacer.endStats();
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
this.restorePauseOnLostFocus(mc);
|
||||
this.restoreShowDebugInfo(mc);
|
||||
this.armed = false;
|
||||
this.stopRecording(mc);
|
||||
this.pendingRequest.set(null);
|
||||
|
||||
@@ -12,11 +12,14 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class FlybyScene {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica/Flyby");
|
||||
private static final String[] NO_COMMANDS = new String[0];
|
||||
private static final Pattern COORD_TOKEN = Pattern.compile("\\$([xyz])([+-]\\d+)?");
|
||||
|
||||
static final String MARKER = "flyby";
|
||||
|
||||
@@ -44,6 +47,23 @@ final class FlybyScene {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
static String expand(String line, int x, int y, int z) {
|
||||
if (line.indexOf('$') < 0) return line;
|
||||
final Matcher matcher = COORD_TOKEN.matcher(line);
|
||||
final StringBuilder sb = new StringBuilder(line.length() + 16);
|
||||
while (matcher.find()) {
|
||||
final int base = switch (matcher.group(1).charAt(0)) {
|
||||
case 'x' -> x;
|
||||
case 'y' -> y;
|
||||
default -> z;
|
||||
};
|
||||
final String offset = matcher.group(2);
|
||||
matcher.appendReplacement(sb, Integer.toString(offset == null ? base : base + Integer.parseInt(offset)));
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static int count(WorldServer world) {
|
||||
final List<Entity> entities = world.loadedEntityList;
|
||||
int total = 0;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.loading;
|
||||
|
||||
import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBootstrap;
|
||||
import cpw.mods.fml.relauncher.FMLLaunchHandler;
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -20,12 +21,14 @@ import java.util.Set;
|
||||
// obfuscated mappings and not SRG mappings when running outside of dev env.
|
||||
// ===============================================
|
||||
@IFMLLoadingPlugin.MCVersion("1.7.10")
|
||||
@IFMLLoadingPlugin.TransformerExclusions("com.gtnewhorizons.angelica.experimental.surround.")
|
||||
public class AngelicaTweaker implements IFMLLoadingPlugin, IEarlyMixinLoader {
|
||||
|
||||
private final IFMLLoadingPlugin loadingPlugin;
|
||||
private final IEarlyMixinLoader mixinLoader;
|
||||
|
||||
public AngelicaTweaker() {
|
||||
SurroundBootstrap.init();
|
||||
if (FMLLaunchHandler.side().isClient()) {
|
||||
final AngelicaClientTweaker clientTweaker = new AngelicaClientTweaker();
|
||||
loadingPlugin = clientTweaker;
|
||||
|
||||
@@ -41,6 +41,7 @@ import com.gtnewhorizons.angelica.client.font.DarkModeUtils;
|
||||
import com.gtnewhorizons.angelica.commands.AngelicaCommand;
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.common.BlockError;
|
||||
import com.gtnewhorizons.angelica.common.BlockIsbrhTest;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.bettercrashes.BetterCrashesCompat;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.CompatMathHelper;
|
||||
@@ -69,6 +70,7 @@ import com.gtnewhorizons.angelica.render.EmissiveTextureAutoloader;
|
||||
import com.gtnewhorizons.angelica.rendering.AngelicaBlockSafetyRegistry;
|
||||
import com.gtnewhorizons.angelica.rendering.FpsReducer;
|
||||
import com.gtnewhorizons.angelica.rendering.FramePacer;
|
||||
import com.gtnewhorizons.angelica.rendering.IsbrhTestRenderer;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasSetup;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
@@ -81,6 +83,7 @@ import com.gtnewhorizons.angelica.rendering.tesr.TesrLifecycle;
|
||||
import com.gtnewhorizons.angelica.utils.AnimationMode;
|
||||
import com.gtnewhorizons.angelica.utils.ManagedEnum;
|
||||
import com.gtnewhorizons.angelica.zoom.Zoom;
|
||||
import cpw.mods.fml.client.registry.RenderingRegistry;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.event.FMLInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
|
||||
@@ -141,6 +144,10 @@ public final class ClientProxy extends CommonProxy {
|
||||
MinecraftForge.EVENT_BUS.register(new AngelicaTesrMeshCache.ReloadListener());
|
||||
ModelRegistry.registerModid(MOD_ID);
|
||||
blockError = new BlockError();
|
||||
if (AngelicaConfig.enableTestBlocks) {
|
||||
BlockIsbrhTest.renderId = RenderingRegistry.getNextAvailableRenderId();
|
||||
RenderingRegistry.registerBlockHandler(BlockIsbrhTest.renderId, new IsbrhTestRenderer());
|
||||
}
|
||||
if (AngelicaConfig.enableIris) {
|
||||
IrisGLSMBridge.installImmediateExtendedHandler();
|
||||
Iris.warmupShaderTransforms();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.proxy;
|
||||
|
||||
import com.gtnewhorizons.angelica.common.BlockIsbrhTest;
|
||||
import com.gtnewhorizons.angelica.common.BlockTest;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import cpw.mods.fml.common.event.FMLInitializationEvent;
|
||||
@@ -12,6 +13,7 @@ public class CommonProxy {
|
||||
public void preInit(FMLPreInitializationEvent event) {
|
||||
if (AngelicaConfig.enableTestBlocks) {
|
||||
GameRegistry.registerBlock(new BlockTest(), "test_block");
|
||||
GameRegistry.registerBlock(new BlockIsbrhTest(), "test_isbrh");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrAttribution;
|
||||
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import net.minecraft.entity.Entity;
|
||||
|
||||
public final class EntityRenderScope {
|
||||
|
||||
private EntityRenderScope() {}
|
||||
|
||||
public static boolean begin(Entity entity, boolean lightning) {
|
||||
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
|
||||
CapturedRenderingState.INSTANCE.pushCurrentEntityColor();
|
||||
CapturedRenderingState.INSTANCE.setCurrentRenderedEntity(entity);
|
||||
TesrAttribution.currentRenderable = entity != null ? entity.getClass() : null;
|
||||
if (lightning) {
|
||||
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.LIGHTNING);
|
||||
}
|
||||
return GbufferPrograms.beginNestedEntityPhase();
|
||||
}
|
||||
|
||||
public static void end(Class<?> prevRenderable, boolean lightning, boolean nested) {
|
||||
GbufferPrograms.endNestedEntityPhase(nested);
|
||||
if (lightning) {
|
||||
GbufferPrograms.teardownSpecialRenderCondition();
|
||||
}
|
||||
CapturedRenderingState.INSTANCE.popCurrentEntityColor();
|
||||
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
|
||||
TesrAttribution.currentRenderable = prevRenderable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Map;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.ThreadSafeISBRH;
|
||||
import com.gtnewhorizons.angelica.api.ThreadSafeISBRHFactory;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
|
||||
public final class IsbrhDispatch {
|
||||
|
||||
private static final byte KIND_MISSING = 0;
|
||||
private static final byte KIND_SHARED = 1;
|
||||
private static final byte KIND_PER_THREAD_CTOR = 2;
|
||||
private static final byte KIND_FACTORY = 3;
|
||||
|
||||
private static final int DENSE_LIMIT = 4096;
|
||||
|
||||
private static final class Entry {
|
||||
|
||||
final ISimpleBlockRenderingHandler handler;
|
||||
final byte kind;
|
||||
|
||||
Entry(ISimpleBlockRenderingHandler handler, byte kind) {
|
||||
this.handler = handler;
|
||||
this.kind = kind;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Entry MISSING = new Entry(null, KIND_MISSING);
|
||||
private static final Object CLASSIFY_LOCK = new Object();
|
||||
|
||||
private static volatile Entry[] entries = new Entry[0];
|
||||
private static volatile int generation;
|
||||
|
||||
private static final class PerThread {
|
||||
|
||||
int gen = -1;
|
||||
ISimpleBlockRenderingHandler[] byId = new ISimpleBlockRenderingHandler[0];
|
||||
final Reference2ObjectOpenHashMap<Class<?>, ISimpleBlockRenderingHandler> byClass = new Reference2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
private static final ThreadLocal<PerThread> PER_THREAD = ThreadLocal.withInitial(PerThread::new);
|
||||
|
||||
private IsbrhDispatch() {}
|
||||
|
||||
public static ISimpleBlockRenderingHandler resolve(Map<Integer, ISimpleBlockRenderingHandler> map, int modelId) {
|
||||
return resolve(map, modelId, Thread.currentThread() == GLStateManager.getMainThread());
|
||||
}
|
||||
|
||||
public static ISimpleBlockRenderingHandler resolve(Map<Integer, ISimpleBlockRenderingHandler> map, int modelId,
|
||||
boolean onMainThread) {
|
||||
if (modelId < 0 || modelId >= DENSE_LIMIT) {
|
||||
ISimpleBlockRenderingHandler handler = map.get(modelId);
|
||||
return handler == null ? null : adaptForCurrentThread(handler, onMainThread);
|
||||
}
|
||||
|
||||
final int gen = generation;
|
||||
Entry e = classify(map, modelId);
|
||||
if (e.kind == KIND_MISSING) return null;
|
||||
if (onMainThread || e.kind == KIND_SHARED) return e.handler;
|
||||
|
||||
PerThread pt = PER_THREAD.get();
|
||||
if (pt.gen != gen) {
|
||||
pt.gen = gen;
|
||||
pt.byId = new ISimpleBlockRenderingHandler[DENSE_LIMIT];
|
||||
}
|
||||
|
||||
ISimpleBlockRenderingHandler cached = pt.byId[modelId];
|
||||
if (cached != null) return cached;
|
||||
|
||||
ISimpleBlockRenderingHandler adapted = adapt(e.handler, e.kind);
|
||||
pt.byId[modelId] = adapted;
|
||||
return adapted;
|
||||
}
|
||||
|
||||
private static ISimpleBlockRenderingHandler adaptForCurrentThread(ISimpleBlockRenderingHandler main, boolean onMainThread) {
|
||||
if (onMainThread || main == null) return main;
|
||||
return adapt(main, classifyKind(main));
|
||||
}
|
||||
|
||||
public static void invalidate() {
|
||||
synchronized (CLASSIFY_LOCK) {
|
||||
generation++;
|
||||
entries = new Entry[entries.length];
|
||||
}
|
||||
}
|
||||
|
||||
private static ISimpleBlockRenderingHandler adapt(ISimpleBlockRenderingHandler main, byte kind) {
|
||||
if (kind == KIND_SHARED) return main;
|
||||
|
||||
Class<?> cls = main.getClass();
|
||||
PerThread pt = PER_THREAD.get();
|
||||
ISimpleBlockRenderingHandler obj = pt.byClass.get(cls);
|
||||
if (obj != null) return obj;
|
||||
|
||||
ISimpleBlockRenderingHandler created;
|
||||
if (kind == KIND_FACTORY) {
|
||||
created = (ISimpleBlockRenderingHandler) ((ThreadSafeISBRHFactory) main).newInstance();
|
||||
} else {
|
||||
try {
|
||||
// Won't work with non-default constructors, use ThreadSafeISBRHFactory instead
|
||||
created = (ISimpleBlockRenderingHandler) cls.getDeclaredConstructor().newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
} catch (InvocationTargetException ex) {
|
||||
throw new RuntimeException(ex.getCause());
|
||||
}
|
||||
}
|
||||
pt.byClass.put(cls, created);
|
||||
return created;
|
||||
}
|
||||
|
||||
private static byte classifyKind(ISimpleBlockRenderingHandler handler) {
|
||||
ThreadSafeISBRH annotation = handler.getClass().getAnnotation(ThreadSafeISBRH.class);
|
||||
if (annotation != null && annotation.perThread()) return KIND_PER_THREAD_CTOR;
|
||||
if (handler instanceof ThreadSafeISBRHFactory) return KIND_FACTORY;
|
||||
return KIND_SHARED;
|
||||
}
|
||||
|
||||
private static Entry classify(Map<Integer, ISimpleBlockRenderingHandler> map, int modelId) {
|
||||
Entry[] arr = entries;
|
||||
if (modelId < arr.length) {
|
||||
Entry e = arr[modelId];
|
||||
if (e != null) return e;
|
||||
}
|
||||
|
||||
synchronized (CLASSIFY_LOCK) {
|
||||
arr = entries;
|
||||
if (modelId >= arr.length) {
|
||||
Entry[] grown = new Entry[DENSE_LIMIT];
|
||||
System.arraycopy(arr, 0, grown, 0, arr.length);
|
||||
arr = grown;
|
||||
}
|
||||
Entry e = arr[modelId];
|
||||
if (e == null) {
|
||||
ISimpleBlockRenderingHandler handler = map.get(modelId);
|
||||
e = handler == null ? MISSING : new Entry(handler, classifyKind(handler));
|
||||
arr[modelId] = e;
|
||||
}
|
||||
entries = arr;
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.ThreadSafeISBRH;
|
||||
import com.gtnewhorizons.angelica.common.BlockIsbrhTest;
|
||||
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
|
||||
@ThreadSafeISBRH(perThread = false)
|
||||
public class IsbrhTestRenderer implements ISimpleBlockRenderingHandler {
|
||||
|
||||
private static volatile boolean crashTestArmed;
|
||||
|
||||
public static void armCrashTest() {
|
||||
crashTestArmed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderInventoryBlock(Block block, int metadata, int modelId, RenderBlocks renderer) {}
|
||||
|
||||
@Override
|
||||
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
|
||||
if (crashTestArmed) {
|
||||
crashTestArmed = false;
|
||||
throw new IllegalStateException("angelica isbrh crashtest");
|
||||
}
|
||||
return renderer.renderStandardBlock(block, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRender3DInInventory(int modelId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRenderId() {
|
||||
return BlockIsbrhTest.renderId;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
public final class OperationArgs {
|
||||
|
||||
private OperationArgs() {}
|
||||
|
||||
public static Object boxed(Object prev, float v) {
|
||||
return prev instanceof Float f && Float.floatToRawIntBits(f) == Float.floatToRawIntBits(v) ? prev : Float.valueOf(v);
|
||||
}
|
||||
|
||||
public static Object boxed(Object prev, int v) {
|
||||
return prev instanceof Integer i && i == v ? prev : Integer.valueOf(v);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrAttribution;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrBlendScope;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrLifecycle;
|
||||
import com.prupe.mcpatcher.ctm.CTMUtils;
|
||||
import net.coderbot.iris.Iris;
|
||||
@@ -35,7 +34,6 @@ public final class RenderRecovery {
|
||||
HUDCaching.resetAfterCrash();
|
||||
mc.getFramebuffer().bindFramebuffer(false);
|
||||
TesrLifecycle.reset();
|
||||
TesrBlendScope.reset();
|
||||
TesrAttribution.currentRenderable = null;
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
|
||||
CTMUtils.clearCurrentCompact();
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.prupe.mcpatcher.ctm.CTMUtils;
|
||||
import jss.notfine.config.MCPatcherForgeConfig;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.BiomeBlendMode;
|
||||
import net.minecraft.block.BlockGrass;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import org.embeddedt.embeddium.api.util.ColorMixer;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
@@ -23,6 +31,11 @@ public final class BiomeVertexBlender {
|
||||
private boolean replacedBlockTint;
|
||||
private final int[] cornerColors = new int[8];
|
||||
private int populatedCorners;
|
||||
private float savedRtl, savedGtl, savedBtl, savedRbl, savedGbl, savedBbl;
|
||||
private float savedRbr, savedGbr, savedBbr, savedRtr, savedGtr, savedBtr;
|
||||
private boolean savedAo;
|
||||
private int savedLtl, savedLbl, savedLbr, savedLtr;
|
||||
private int savedColor, savedBrightness;
|
||||
|
||||
public void setup(SmoothBiomeColorCache cache, SmoothBiomeColorCache.ColorType type, int x, int y, int z) {
|
||||
this.cache = cache;
|
||||
@@ -63,6 +76,64 @@ public final class BiomeVertexBlender {
|
||||
return 0xFFFFFF;
|
||||
}
|
||||
|
||||
public boolean beginFace(RenderBlocks renderer, int face, double x, double y, double z, IIcon icon) {
|
||||
if (!isActive()) return false;
|
||||
final Tessellator tessellator = TessellatorManager.get();
|
||||
if (((BiomeBlendTessellator) tessellator).angelica$getBiomeBlender() != this) return false;
|
||||
final IIcon originalIcon = AngelicaConfig.enableMCPatcherForgeFeatures && MCPatcherForgeConfig.ConnectedTextures.enabled ? CTMUtils.getOriginalIcon(icon) : icon;
|
||||
if (isGrass() && icon != Blocks.grass.getIcon(1, 0) && icon != BlockGrass.getIconSideOverlay()
|
||||
&& originalIcon != Blocks.grass.getIcon(1, 0) && originalIcon != BlockGrass.getIconSideOverlay()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
savedRtl = renderer.colorRedTopLeft; savedGtl = renderer.colorGreenTopLeft; savedBtl = renderer.colorBlueTopLeft;
|
||||
savedRbl = renderer.colorRedBottomLeft; savedGbl = renderer.colorGreenBottomLeft; savedBbl = renderer.colorBlueBottomLeft;
|
||||
savedRbr = renderer.colorRedBottomRight; savedGbr = renderer.colorGreenBottomRight; savedBbr = renderer.colorBlueBottomRight;
|
||||
savedRtr = renderer.colorRedTopRight; savedGtr = renderer.colorGreenTopRight; savedBtr = renderer.colorBlueTopRight;
|
||||
final boolean ao = savedAo = renderer.enableAO;
|
||||
savedLtl = renderer.brightnessTopLeft; savedLbl = renderer.brightnessBottomLeft;
|
||||
savedLbr = renderer.brightnessBottomRight; savedLtr = renderer.brightnessTopRight;
|
||||
final int color = savedColor = tessellator.color;
|
||||
final int brightness = savedBrightness = tessellator.brightness;
|
||||
if (!ao) {
|
||||
final int abgr = nativeToABGR(color);
|
||||
renderer.colorRedTopLeft = renderer.colorRedBottomLeft = renderer.colorRedBottomRight = renderer.colorRedTopRight = (abgr & 255) / 255.0f;
|
||||
renderer.colorGreenTopLeft = renderer.colorGreenBottomLeft = renderer.colorGreenBottomRight = renderer.colorGreenTopRight = (abgr >> 8 & 255) / 255.0f;
|
||||
renderer.colorBlueTopLeft = renderer.colorBlueBottomLeft = renderer.colorBlueBottomRight = renderer.colorBlueTopRight = (abgr >> 16 & 255) / 255.0f;
|
||||
renderer.brightnessTopLeft = renderer.brightnessBottomLeft = renderer.brightnessBottomRight = renderer.brightnessTopRight = brightness;
|
||||
renderer.enableAO = true;
|
||||
}
|
||||
try {
|
||||
tintFace(renderer, face, x, y, z);
|
||||
} catch (Throwable t) {
|
||||
endFace(renderer);
|
||||
throw t;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void endFace(RenderBlocks renderer) {
|
||||
renderer.colorRedTopLeft = savedRtl; renderer.colorGreenTopLeft = savedGtl; renderer.colorBlueTopLeft = savedBtl;
|
||||
renderer.colorRedBottomLeft = savedRbl; renderer.colorGreenBottomLeft = savedGbl; renderer.colorBlueBottomLeft = savedBbl;
|
||||
renderer.colorRedBottomRight = savedRbr; renderer.colorGreenBottomRight = savedGbr; renderer.colorBlueBottomRight = savedBbr;
|
||||
renderer.colorRedTopRight = savedRtr; renderer.colorGreenTopRight = savedGtr; renderer.colorBlueTopRight = savedBtr;
|
||||
renderer.enableAO = savedAo;
|
||||
if (!savedAo) {
|
||||
renderer.brightnessTopLeft = savedLtl; renderer.brightnessBottomLeft = savedLbl;
|
||||
renderer.brightnessBottomRight = savedLbr; renderer.brightnessTopRight = savedLtr;
|
||||
final Tessellator tessellator = TessellatorManager.get();
|
||||
tessellator.color = savedColor;
|
||||
tessellator.brightness = savedBrightness;
|
||||
}
|
||||
}
|
||||
|
||||
public static long smoothColor(IBlockAccess access, SmoothBiomeColorCache.ColorType type, int x, int y, int z) {
|
||||
final SmoothBiomeColorCache cache = SmoothBiomeColorCache.getActiveCache();
|
||||
if (cache != null) return cache.getColor(type, x, y, z) & 0xFFFFFFFFL;
|
||||
if (access instanceof WorldClientExtension ext) return ext.celeritas$getSmoothBiomeColorCache().getColor(type, x, y, z) & 0xFFFFFFFFL;
|
||||
return -1L;
|
||||
}
|
||||
|
||||
public void tintFace(RenderBlocks renderer, int face, double x, double y, double z) {
|
||||
final int[] corners = FACE_CORNERS[face];
|
||||
final int tl = faceColor(renderer, face, corners[0], x, y, z);
|
||||
|
||||
+19
-1
@@ -41,20 +41,38 @@ public class BlockRenderListManager {
|
||||
return itemFrameDisplayLists[variant];
|
||||
}
|
||||
|
||||
public static int startCompiling() {
|
||||
public static int callOrStartCompiling(int cached) {
|
||||
if (cached != 0) {
|
||||
GLStateManager.glCallList(cached);
|
||||
return -1;
|
||||
}
|
||||
return startCompiling();
|
||||
}
|
||||
|
||||
private static int startCompiling() {
|
||||
final int list = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
return list;
|
||||
}
|
||||
|
||||
public static void endCompiling(int list, Block block, int meta) {
|
||||
if (list <= 0) return;
|
||||
finishCompiling();
|
||||
displayListMap.put(new BlockMeta(block, meta), list);
|
||||
GLStateManager.glCallList(list);
|
||||
}
|
||||
|
||||
public static void endItemFrameCompiling(int list, int variant) {
|
||||
if (list <= 0) return;
|
||||
finishCompiling();
|
||||
itemFrameDisplayLists[variant] = list;
|
||||
GLStateManager.glCallList(list);
|
||||
}
|
||||
|
||||
public static void abortCompiling(int list) {
|
||||
if (list <= 0) return;
|
||||
finishCompiling();
|
||||
GLStateManager.glDeleteLists(list, 1);
|
||||
}
|
||||
|
||||
private static void finishCompiling() {
|
||||
|
||||
+40
-73
@@ -6,7 +6,6 @@ import com.gtnewhorizons.angelica.api.tesr.TesrMaterial;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import com.gtnewhorizons.angelica.rendering.OperationArgs;
|
||||
import com.gtnewhorizons.angelica.rendering.items.BlockRenderListManager.BlockMeta;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.AngelicaTesrMeshCache;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BakedTransformCapture;
|
||||
@@ -16,24 +15,24 @@ import com.gtnewhorizons.angelica.rendering.tesr.GlintCapture;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TemplateBuffer;
|
||||
import com.gtnewhorizons.angelica.utils.AnimationsRenderUtils;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.renderer.ItemRenderer;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
public final class DroppedItemInstancer {
|
||||
|
||||
public static final long SKIP = -2L;
|
||||
public static final long RUN = -1L;
|
||||
|
||||
private static final int CAPTURE_BYTES = 64 * 1024;
|
||||
|
||||
private static final ItemPropCache<IconMesh> icons = new ItemPropCache<>(() -> AngelicaConfig.itemRendererCacheSize, IconMesh::new, null);
|
||||
private static final Object2ObjectOpenHashMap<BlockMeta, BlockMesh> blocks = new Object2ObjectOpenHashMap<>();
|
||||
private static final BlockMeta blockKey = new BlockMeta();
|
||||
private static final AngelicaTesrMeshCache.GtnhMeshBackend iconBackend = new AngelicaTesrMeshCache.GtnhMeshBackend();
|
||||
private static final Object[] ICON_ARGS = new Object[8];
|
||||
private static final Object[] GLINT_ARGS = new Object[8];
|
||||
private static final Object[] BLOCK_ARGS = new Object[4];
|
||||
private static BakedTransformCapture blockBackend;
|
||||
private static boolean basePart;
|
||||
private static final GlintCapture glintCapture = new GlintCapture();
|
||||
@@ -108,62 +107,59 @@ public final class DroppedItemInstancer {
|
||||
return material;
|
||||
}
|
||||
|
||||
public static void icon(ItemStack stack, boolean afterGlint, Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
|
||||
public static long icon(ItemStack stack, boolean afterGlint, float maxU, float minV, float minU, float maxV, int width, int height, float thickness) {
|
||||
glintCapture.reset();
|
||||
glintSeen = false;
|
||||
final TesrMaterial material = material(stack, true);
|
||||
if (material == null) {
|
||||
basePart = false;
|
||||
callIcon(ICON_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
|
||||
return;
|
||||
return RUN;
|
||||
}
|
||||
basePart = batchIcon(material, afterGlint, t, maxU, minV, minU, maxV, width, height, thickness, original);
|
||||
final long part = batchIcon(material, afterGlint, maxU, minV, minU, maxV, width, height, thickness);
|
||||
basePart = part == SKIP;
|
||||
return part;
|
||||
}
|
||||
|
||||
static boolean batchIcon(TesrMaterial material, boolean afterGlint, Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
|
||||
static long batchIcon(TesrMaterial material, boolean afterGlint, float maxU, float minV, float minU, float maxV, int width, int height, float thickness) {
|
||||
final long drawsBefore = GLStateManager.drawCalls;
|
||||
if (BatchEligibility.batchingAllowed()) {
|
||||
final TemplateBuffer template = iconTemplate(ICON_ARGS, maxU, minV, minU, maxV, width, height, thickness, original);
|
||||
final TemplateBuffer template = iconTemplate(maxU, minV, minU, maxV, width, height, thickness);
|
||||
if (template == null) {
|
||||
callIcon(ICON_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
|
||||
fallback++;
|
||||
bail(BailReason.TEMPLATE);
|
||||
return false;
|
||||
return RUN;
|
||||
}
|
||||
if (ModelPartBatcher.INSTANCE.queueTemplate(template, material, afterGlint)) {
|
||||
instanced++;
|
||||
BatchEligibility.onPartQueued();
|
||||
return true;
|
||||
return SKIP;
|
||||
}
|
||||
fallback++;
|
||||
bail(BailReason.QUEUE);
|
||||
} else {
|
||||
bail(BailReason.NOT_ALLOWED);
|
||||
}
|
||||
callIcon(ICON_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
|
||||
BatchEligibility.onPartFallback(drawsBefore, GLStateManager.drawCalls);
|
||||
return false;
|
||||
return drawsBefore;
|
||||
}
|
||||
|
||||
/** Held glints use ITEM_GLINT for its stencil guard; dropped items draw with culling on and need none. */
|
||||
public static void glint(Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, TesrMaterial material, Operation<Void> original) {
|
||||
public static long glint(float maxU, float minV, float minU, float maxV, int width, int height, float thickness, TesrMaterial material) {
|
||||
if (!basePart) {
|
||||
callIcon(GLINT_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
|
||||
return;
|
||||
return RUN;
|
||||
}
|
||||
final boolean allowed = BatchEligibility.batchingAllowed();
|
||||
if (allowed && ModelPartBatcher.INSTANCE.isShadowPass()) {
|
||||
BatchEligibility.onPartQueued();
|
||||
return;
|
||||
return SKIP;
|
||||
}
|
||||
final long drawsBefore = GLStateManager.drawCalls;
|
||||
if (allowed) {
|
||||
currentGlintProperties.set(maxU, minV, minU, maxV, width, height, thickness);
|
||||
if (glintSeen && glintProperties.equals(currentGlintProperties) && ModelPartBatcher.INSTANCE.replayGlint(glintCapture)) {
|
||||
glintInstanced++;
|
||||
return;
|
||||
return SKIP;
|
||||
}
|
||||
final TemplateBuffer template = iconTemplate(GLINT_ARGS, maxU, minV, minU, maxV, width, height, thickness, original);
|
||||
final TemplateBuffer template = iconTemplate(maxU, minV, minU, maxV, width, height, thickness);
|
||||
final boolean capture = !glintSeen && ModelPartBatcher.INSTANCE.beginGlintCapture(glintCapture);
|
||||
if (capture) glintProperties.set(currentGlintProperties);
|
||||
glintSeen = true;
|
||||
@@ -176,15 +172,14 @@ public final class DroppedItemInstancer {
|
||||
if (queued) {
|
||||
glintInstanced++;
|
||||
BatchEligibility.onPartQueued();
|
||||
return;
|
||||
return SKIP;
|
||||
}
|
||||
fallback++;
|
||||
bail(template == null ? BailReason.TEMPLATE : BailReason.QUEUE);
|
||||
} else {
|
||||
bail(BailReason.NOT_ALLOWED);
|
||||
}
|
||||
callIcon(GLINT_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
|
||||
BatchEligibility.onPartFallback(drawsBefore, GLStateManager.drawCalls);
|
||||
return drawsBefore;
|
||||
}
|
||||
|
||||
public static boolean queueSkippedGlint(int iconWidth, int iconHeight) {
|
||||
@@ -203,50 +198,52 @@ public final class DroppedItemInstancer {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void block(ItemStack stack, RenderBlocks rb, Block block, int meta, float brightness, boolean unfilteredAtlas, Operation<Void> original) {
|
||||
public static long block(ItemStack stack, RenderBlocks rb, Block block, int meta, float brightness, boolean unfilteredAtlas) {
|
||||
final TesrMaterial material = material(stack, unfilteredAtlas);
|
||||
if (material == null) {
|
||||
callBlock(original, rb, block, meta, brightness);
|
||||
return;
|
||||
return RUN;
|
||||
}
|
||||
final BailReason stateBail = blockStateBail(rb, block, brightness);
|
||||
if (stateBail != null) {
|
||||
fallback++;
|
||||
bail(stateBail);
|
||||
callBlock(original, rb, block, meta, brightness);
|
||||
return;
|
||||
return RUN;
|
||||
}
|
||||
batchBlock(material, rb, block, meta, brightness, original);
|
||||
return batchBlock(material, rb, block, meta, brightness);
|
||||
}
|
||||
|
||||
static void batchBlock(TesrMaterial material, RenderBlocks rb, Block block, int meta, float brightness, Operation<Void> original) {
|
||||
static long batchBlock(TesrMaterial material, RenderBlocks rb, Block block, int meta, float brightness) {
|
||||
final long drawsBefore = GLStateManager.drawCalls;
|
||||
if (BatchEligibility.batchingAllowed()) {
|
||||
BlockMesh mesh = lookupBlock(block, meta);
|
||||
if (mesh == null) {
|
||||
mesh = captureBlock(rb, block, meta, brightness, original);
|
||||
mesh = captureBlock(rb, block, meta, brightness);
|
||||
} else if (mesh.template() != null) {
|
||||
GLStateManager.glColor4f(mesh.red(), mesh.green(), mesh.blue(), mesh.alpha());
|
||||
}
|
||||
if (mesh.template() == null) {
|
||||
callBlock(original, rb, block, meta, brightness);
|
||||
fallback++;
|
||||
bail(BailReason.TEMPLATE);
|
||||
return;
|
||||
return RUN;
|
||||
}
|
||||
if (ModelPartBatcher.INSTANCE.queueTemplate(mesh.template(), material)) {
|
||||
mesh.sprites().markUsed();
|
||||
instanced++;
|
||||
BatchEligibility.onPartQueued();
|
||||
return;
|
||||
return SKIP;
|
||||
}
|
||||
fallback++;
|
||||
bail(BailReason.QUEUE);
|
||||
} else {
|
||||
bail(BailReason.NOT_ALLOWED);
|
||||
}
|
||||
callBlock(original, rb, block, meta, brightness);
|
||||
BatchEligibility.onPartFallback(drawsBefore, GLStateManager.drawCalls);
|
||||
return drawsBefore;
|
||||
}
|
||||
|
||||
public static void endPart(long part) {
|
||||
if (part >= 0L) {
|
||||
BatchEligibility.onPartFallback(part, GLStateManager.drawCalls);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
@@ -263,43 +260,13 @@ public final class DroppedItemInstancer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void callIcon(Object[] args, Operation<Void> original, Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness) {
|
||||
args[0] = t;
|
||||
args[1] = OperationArgs.boxed(args[1], maxU);
|
||||
args[2] = OperationArgs.boxed(args[2], minV);
|
||||
args[3] = OperationArgs.boxed(args[3], minU);
|
||||
args[4] = OperationArgs.boxed(args[4], maxV);
|
||||
args[5] = OperationArgs.boxed(args[5], width);
|
||||
args[6] = OperationArgs.boxed(args[6], height);
|
||||
args[7] = OperationArgs.boxed(args[7], thickness);
|
||||
try {
|
||||
original.call(args);
|
||||
} finally {
|
||||
args[0] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void callBlock(Operation<Void> original, RenderBlocks rb, Block block, int meta, float brightness) {
|
||||
final Object[] args = BLOCK_ARGS;
|
||||
args[0] = rb;
|
||||
args[1] = block;
|
||||
args[2] = OperationArgs.boxed(args[2], meta);
|
||||
args[3] = OperationArgs.boxed(args[3], brightness);
|
||||
try {
|
||||
original.call(args);
|
||||
} finally {
|
||||
args[0] = null;
|
||||
args[1] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static TemplateBuffer iconTemplate(Object[] args, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
|
||||
private static TemplateBuffer iconTemplate(float maxU, float minV, float minU, float maxV, int width, int height, float thickness) {
|
||||
final IconMesh hit = icons.get(maxU, minV, minU, maxV, width, height, thickness);
|
||||
if (hit != null) return hit.template;
|
||||
final Tessellator capture = iconBackend.beginCapture(DefaultVertexFormat.POSITION_TEXTURE_NORMAL);
|
||||
final TemplateBuffer template;
|
||||
try {
|
||||
callIcon(args, original, capture, maxU, minV, minU, maxV, width, height, thickness);
|
||||
ItemRenderer.renderItemIn2D(capture, maxU, minV, minU, maxV, width, height, thickness);
|
||||
} finally {
|
||||
template = iconBackend.endCaptureToTemplate();
|
||||
}
|
||||
@@ -308,7 +275,7 @@ public final class DroppedItemInstancer {
|
||||
return template;
|
||||
}
|
||||
|
||||
private static BlockMesh captureBlock(RenderBlocks rb, Block block, int meta, float brightness, Operation<Void> original) {
|
||||
private static BlockMesh captureBlock(RenderBlocks rb, Block block, int meta, float brightness) {
|
||||
if (blockBackend == null) {
|
||||
blockBackend = new BakedTransformCapture(CAPTURE_BYTES);
|
||||
}
|
||||
@@ -318,7 +285,7 @@ public final class DroppedItemInstancer {
|
||||
try {
|
||||
blockBackend.begin();
|
||||
try {
|
||||
callBlock(original, rb, block, meta, brightness);
|
||||
rb.renderBlockAsItem(block, meta, brightness);
|
||||
} finally {
|
||||
template = blockBackend.end();
|
||||
}
|
||||
|
||||
+6
-29
@@ -15,12 +15,10 @@ import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.profiling.BailClassCounts;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.gtnewhorizons.angelica.rendering.OperationArgs;
|
||||
import com.gtnewhorizons.angelica.rendering.ParticleRunSplitter;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.InstanceRing;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.MeshBuffer;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
@@ -47,7 +45,6 @@ public final class ParticleInstancer {
|
||||
private static final ParticleRenderState STATE = new ParticleRenderState();
|
||||
private static int restoreDepth = -1;
|
||||
private static final Matrix4f LAYER_MV = new Matrix4f();
|
||||
private static final Object[] ORIGINAL_ARGS = new Object[8];
|
||||
|
||||
private static final ObjectArrayList<Group> GROUP_POOL = new ObjectArrayList<>();
|
||||
private static int groupCount;
|
||||
@@ -116,16 +113,14 @@ public final class ParticleInstancer {
|
||||
}
|
||||
}
|
||||
|
||||
public static void renderParticle(EntityFX fx, Tessellator runTessellator, float partialTicks, float rotationX, float rotationXZ, float rotationZ, float rotationYZ, float rotationXY, Operation<Void> original) {
|
||||
public static boolean renderParticle(EntityFX fx, Tessellator runTessellator, float partialTicks, float rotationX, float rotationXZ, float rotationZ, float rotationYZ, float rotationXY) {
|
||||
if (!layerStarted) beginLayer();
|
||||
if (!layerActive) {
|
||||
call(fx, runTessellator, partialTicks, rotationX, rotationXZ, rotationZ, rotationYZ, rotationXY, original);
|
||||
return;
|
||||
}
|
||||
renderActive(fx, runTessellator, partialTicks, rotationX, rotationXZ, rotationZ, rotationYZ, rotationXY, original);
|
||||
if (!layerActive) return false;
|
||||
renderActive(fx, runTessellator, partialTicks, rotationX, rotationXZ, rotationZ, rotationYZ, rotationXY);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void renderActive(EntityFX fx, Tessellator runTessellator, float partialTicks, float rotationX, float rotationXZ, float rotationZ, float rotationYZ, float rotationXY, Operation<Void> original) {
|
||||
private static void renderActive(EntityFX fx, Tessellator runTessellator, float partialTicks, float rotationX, float rotationXZ, float rotationZ, float rotationYZ, float rotationXY) {
|
||||
|
||||
run = runTessellator;
|
||||
rotX = rotationX;
|
||||
@@ -148,7 +143,7 @@ public final class ParticleInstancer {
|
||||
}
|
||||
|
||||
CAPTURE.begin(runTessellator.brightness, runTessellator, LAYER_MV);
|
||||
call(fx, CAPTURE, partialTicks, rotationX, rotationXZ, rotationZ, rotationYZ, rotationXY, original);
|
||||
fx.renderParticle(CAPTURE, partialTicks, rotationX, rotationXZ, rotationZ, rotationYZ, rotationXY);
|
||||
CAPTURE.propagateColor();
|
||||
|
||||
if (CAPTURE.spilled()) {
|
||||
@@ -268,24 +263,6 @@ public final class ParticleInstancer {
|
||||
ParticleDescriptorRegistry.clearCache();
|
||||
}
|
||||
|
||||
private static void call(EntityFX fx, Tessellator target, float partialTicks, float rotationX, float rotationXZ, float rotationZ, float rotationYZ, float rotationXY, Operation<Void> original) {
|
||||
final Object[] args = ORIGINAL_ARGS;
|
||||
args[0] = fx;
|
||||
args[1] = target;
|
||||
args[2] = OperationArgs.boxed(args[2], partialTicks);
|
||||
args[3] = OperationArgs.boxed(args[3], rotationX);
|
||||
args[4] = OperationArgs.boxed(args[4], rotationXZ);
|
||||
args[5] = OperationArgs.boxed(args[5], rotationZ);
|
||||
args[6] = OperationArgs.boxed(args[6], rotationYZ);
|
||||
args[7] = OperationArgs.boxed(args[7], rotationXY);
|
||||
try {
|
||||
original.call(args);
|
||||
} finally {
|
||||
args[0] = null;
|
||||
args[1] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean available() {
|
||||
if (deferred != null && deferred == unavailableFor) return false;
|
||||
if (!ShaderManager.getInstance().isEnabled()) return false;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class TesrBlendScope {
|
||||
|
||||
private TesrBlendScope() {}
|
||||
|
||||
private static int[] depths = new int[4];
|
||||
private static int depth;
|
||||
|
||||
public static void enter() {
|
||||
if (depth == depths.length) {
|
||||
depths = Arrays.copyOf(depths, depth * 2);
|
||||
}
|
||||
depths[depth++] = GLStateManager.pushState(StateSet.BLEND);
|
||||
}
|
||||
|
||||
public static void exit() {
|
||||
if (depth == 0) return;
|
||||
GLStateManager.popStateTo(depths[--depth]);
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
depth = 0;
|
||||
}
|
||||
}
|
||||
@@ -68,13 +68,12 @@ public class ConcurrentTileEntityMap extends HashMap<ChunkPosition, TileEntity>
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
|
||||
public void withWriteLock(Runnable action) {
|
||||
public void writeLock() {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
action.run();
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeUnlock() {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
|
||||
public TileEntity putDirect(ChunkPosition key, TileEntity value) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package net.coderbot.iris.uniforms;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
import net.minecraft.block.Block;
|
||||
@@ -18,6 +21,24 @@ public class ItemIdManager {
|
||||
CapturedRenderingState.INSTANCE.popCurrentRenderedItem();
|
||||
}
|
||||
|
||||
public static int beginCutout(ItemStack stack) {
|
||||
pushItemId();
|
||||
final int depth = GLStateManager.pushState(StateSet.CUTOUT);
|
||||
try {
|
||||
GbufferPrograms.setCutoutDefaults();
|
||||
setItemId(stack);
|
||||
} catch (Throwable t) {
|
||||
endCutout(depth);
|
||||
throw t;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
public static void endCutout(int depth) {
|
||||
popItemId();
|
||||
GLStateManager.popStateTo(depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the item ID for an armor piece or held item.
|
||||
* If the ItemStack is null/empty, resets to 0.
|
||||
|
||||
+12
@@ -1,6 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.client.rendering.ThreadedBlockData;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -14,4 +16,14 @@ public class MixinMinecraftServer {
|
||||
private void angelica$trackTicktimes(CallbackInfo ci, @Local(ordinal = 0) long preTime) {
|
||||
AngelicaMod.proxy.putTicktime(System.nanoTime() - preTime);
|
||||
}
|
||||
|
||||
@Surround(method = "run", remap = false)
|
||||
private void angelica$setServerThread() {
|
||||
ThreadedBlockData.serverThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
@Surround.Finally
|
||||
private void angelica$clearServerThread() {
|
||||
ThreadedBlockData.serverThread = null;
|
||||
}
|
||||
}
|
||||
+96
-99
@@ -1,14 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.CombinedGlint;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.rendering.items.HeldItemGlint;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.coderbot.iris.pipeline.ShadowRenderer;
|
||||
import net.minecraft.client.renderer.ItemRenderer;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
@@ -20,33 +18,24 @@ import org.lwjgl.opengl.GL11;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/** Match item depth and prevent coplanar extrusion faces from blending the same glint layer twice. */
|
||||
@Mixin(value = ItemRenderer.class, priority = 1100)
|
||||
public class MixinItemRenderer_EdgeDepth {
|
||||
|
||||
@Unique private static final String RENDER_ITEM = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V";
|
||||
@Unique private static final Tracy.ZoneId angelica$Z_HELD_ITEM = Tracy.zoneId("heldItem", Tracy.COLOR_CLIENT);
|
||||
@Unique private static IIcon angelica$glintIcon;
|
||||
@Unique private static boolean angelica$combinedGlint;
|
||||
@Unique private static boolean angelica$stencilPrepared;
|
||||
@Unique private static boolean angelica$preparingStencil;
|
||||
@Unique private static boolean angelica$firstLayerMarked;
|
||||
@Unique private static int angelica$nesting;
|
||||
@Unique private static final IIcon[] angelica$savedIcons = new IIcon[8];
|
||||
@Unique private static final boolean[] angelica$savedCombined = new boolean[8];
|
||||
@Unique private static final boolean[] angelica$savedPrepared = new boolean[8];
|
||||
@Unique private static boolean angelica$drawingGlint;
|
||||
|
||||
@Inject(method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V",
|
||||
at = @At("HEAD"), remap = false)
|
||||
private void angelica$enterRenderItem(CallbackInfo ci) {
|
||||
final int depth = angelica$nesting++;
|
||||
if (depth < angelica$savedIcons.length) {
|
||||
angelica$savedIcons[depth] = angelica$glintIcon;
|
||||
angelica$savedCombined[depth] = angelica$combinedGlint;
|
||||
angelica$savedPrepared[depth] = angelica$stencilPrepared;
|
||||
}
|
||||
@Surround(method = RENDER_ITEM, id = "heldItem", remap = false)
|
||||
private void angelica$enterRenderItem(EntityLivingBase entity, ItemStack stack, int pass, ItemRenderType type) {
|
||||
@Surround.Carry final IIcon glintIcon = angelica$glintIcon;
|
||||
@Surround.Carry("combined") final boolean combinedGlint = angelica$combinedGlint;
|
||||
@Surround.Carry("prepared") final boolean stencilPrepared = angelica$stencilPrepared;
|
||||
angelica$glintIcon = null;
|
||||
angelica$combinedGlint = false;
|
||||
angelica$stencilPrepared = false;
|
||||
@@ -54,30 +43,27 @@ public class MixinItemRenderer_EdgeDepth {
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(angelica$Z_HELD_ITEM);
|
||||
}
|
||||
|
||||
@Inject(method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V",
|
||||
at = @At("RETURN"), remap = false)
|
||||
private void angelica$exitRenderItem(CallbackInfo ci) {
|
||||
@Surround.Finally("heldItem")
|
||||
private void angelica$exitRenderItem(@Surround.Carry IIcon glintIcon, @Surround.Carry("combined") boolean combinedGlint,
|
||||
@Surround.Carry("prepared") boolean stencilPrepared) {
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
final int depth = --angelica$nesting;
|
||||
final boolean saved = depth >= 0 && depth < angelica$savedIcons.length;
|
||||
angelica$glintIcon = saved ? angelica$savedIcons[depth] : null;
|
||||
angelica$combinedGlint = saved && angelica$savedCombined[depth];
|
||||
angelica$stencilPrepared = saved && angelica$savedPrepared[depth];
|
||||
angelica$glintIcon = glintIcon;
|
||||
angelica$combinedGlint = combinedGlint;
|
||||
angelica$stencilPrepared = stencilPrepared;
|
||||
angelica$firstLayerMarked = false;
|
||||
if (saved) angelica$savedIcons[depth] = null;
|
||||
if (depth < 0) angelica$nesting = 0;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V", ordinal = 0, remap = true), remap = false)
|
||||
private void angelica$prepareGlintStencil(EntityLivingBase entity, ItemStack stack, int pass, ItemRenderType type, CallbackInfo ci,
|
||||
@Local Tessellator tess) {
|
||||
angelica$preparingStencil = !ShadowRenderer.ACTIVE && !GLStateManager.isRecordingDisplayList()
|
||||
@Surround(method = RENDER_ITEM,
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V", ordinal = 0, remap = true),
|
||||
id = "stencil", remap = false)
|
||||
private void angelica$prepareGlintStencil(Tessellator tess,
|
||||
@Surround.Local(argsOnly = true) ItemStack stack, @Surround.Local(argsOnly = true) int pass) {
|
||||
@Surround.Carry
|
||||
final boolean preparing = !ShadowRenderer.ACTIVE && !GLStateManager.isRecordingDisplayList()
|
||||
&& !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(tess)
|
||||
&& GLStateManager.getDepthTest().isEnabled() && GLStateManager.getDepthState().isEnabled()
|
||||
&& HeldItemGlint.canResetStencil() && HeldItemGlint.eligible() && HeldItemGlint.needsImmediateBase(stack, pass);
|
||||
if (angelica$preparingStencil) {
|
||||
if (preparing) {
|
||||
BatchStateGuard.suspend();
|
||||
try {
|
||||
GLStateManager.glPushAttrib(GL11.GL_STENCIL_BUFFER_BIT);
|
||||
@@ -91,13 +77,9 @@ public class MixinItemRenderer_EdgeDepth {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V", ordinal = 0, remap = true, shift = At.Shift.AFTER),
|
||||
remap = false
|
||||
)
|
||||
private void angelica$finishGlintStencil(CallbackInfo ci) {
|
||||
if (angelica$preparingStencil) {
|
||||
@Surround.Finally("stencil")
|
||||
private void angelica$finishGlintStencil(@Surround.Carry boolean preparing) {
|
||||
if (preparing) {
|
||||
BatchStateGuard.suspend();
|
||||
try {
|
||||
GLStateManager.glPopAttrib();
|
||||
@@ -105,68 +87,83 @@ public class MixinItemRenderer_EdgeDepth {
|
||||
BatchStateGuard.resume();
|
||||
}
|
||||
}
|
||||
angelica$stencilPrepared = angelica$preparingStencil;
|
||||
angelica$preparingStencil = false;
|
||||
angelica$stencilPrepared = preparing;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V", ordinal = 0, remap = true, shift = At.Shift.AFTER),
|
||||
remap = false
|
||||
)
|
||||
private void angelica$rememberGlintIcon(CallbackInfo ci, @Local IIcon icon) {
|
||||
@Surround.Return("stencil")
|
||||
private void angelica$rememberGlintIcon(@Surround.Local IIcon icon) {
|
||||
angelica$glintIcon = icon;
|
||||
}
|
||||
|
||||
// Wrap the geometry method so mods redirecting the caller retain their glint color and blending.
|
||||
@WrapMethod(method = "renderItemIn2D")
|
||||
// Surround the geometry method so mods redirecting the caller retain their glint color and blending.
|
||||
@Surround(method = "renderItemIn2D", id = "glintGeometry")
|
||||
private static void angelica$matchGlintGeometry(Tessellator tess, float minU, float minV, float maxU, float maxV,
|
||||
int width, int height, float thickness, Operation<Void> original) {
|
||||
if (angelica$glintIcon != null && thickness == 0.0625F
|
||||
&& ((width == 256 && height == 256)
|
||||
|| (width == angelica$glintIcon.getIconWidth() && height == angelica$glintIcon.getIconHeight()))
|
||||
&& minU == 0 && minV == 0 && maxU == 1 && maxV == 1) {
|
||||
width = angelica$glintIcon.getIconWidth();
|
||||
height = angelica$glintIcon.getIconHeight();
|
||||
if (!ShadowRenderer.ACTIVE && !GLStateManager.isRecordingDisplayList()
|
||||
&& !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(tess)) {
|
||||
if (angelica$combinedGlint) return;
|
||||
final boolean secondLayer = angelica$firstLayerMarked;
|
||||
BatchStateGuard.suspend();
|
||||
try {
|
||||
final boolean combined = !secondLayer && HeldItemGlint.begin();
|
||||
// Depth selects the surface, stencil limits it to one blend.
|
||||
// The base draw's stencil reset or the first layer's marks avoid a masked full-screen clear.
|
||||
GLStateManager.glPushAttrib(GL11.GL_STENCIL_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
try {
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
|
||||
GLStateManager.glStencilMask(1);
|
||||
if (secondLayer) {
|
||||
// Same pixels as the first layer, so removing its marks leaves the stencil reset.
|
||||
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 1);
|
||||
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_DECR);
|
||||
} else {
|
||||
if (!angelica$stencilPrepared) {
|
||||
GLStateManager.glClearStencil(0);
|
||||
GLStateManager.glClear(GL11.GL_STENCIL_BUFFER_BIT);
|
||||
}
|
||||
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 0, 1);
|
||||
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_INCR);
|
||||
}
|
||||
original.call(tess, minU, minV, maxU, maxV, width, height, thickness);
|
||||
} finally {
|
||||
GLStateManager.glPopAttrib();
|
||||
if (combined) CombinedGlint.end();
|
||||
}
|
||||
angelica$combinedGlint = combined;
|
||||
angelica$firstLayerMarked = !combined && !secondLayer && HeldItemGlint.layersCoverSamePixels();
|
||||
} finally {
|
||||
BatchStateGuard.resume();
|
||||
}
|
||||
return;
|
||||
}
|
||||
int width, int height, float thickness) {
|
||||
@Surround.Skip
|
||||
final boolean drawn = !angelica$drawingGlint && angelica$drawGlintGeometry(tess, minU, minV, maxU, maxV, width, height, thickness);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean angelica$drawGlintGeometry(Tessellator tess, float minU, float minV, float maxU, float maxV,
|
||||
int width, int height, float thickness) {
|
||||
final IIcon icon = angelica$glintIcon;
|
||||
if (icon == null || thickness != 0.0625F
|
||||
|| !((width == 256 && height == 256) || (width == icon.getIconWidth() && height == icon.getIconHeight()))
|
||||
|| minU != 0 || minV != 0 || maxU != 1 || maxV != 1) {
|
||||
return false;
|
||||
}
|
||||
final int iconWidth = icon.getIconWidth();
|
||||
final int iconHeight = icon.getIconHeight();
|
||||
if (ShadowRenderer.ACTIVE || GLStateManager.isRecordingDisplayList()
|
||||
|| TessellatorManager.isCurrentlyCapturing() || TessellatorManager.shouldInterceptDraw(tess)) {
|
||||
if (width == iconWidth && height == iconHeight) return false;
|
||||
angelica$drawGlint(tess, iconWidth, iconHeight, thickness);
|
||||
return true;
|
||||
}
|
||||
if (angelica$combinedGlint) return true;
|
||||
final boolean secondLayer = angelica$firstLayerMarked;
|
||||
BatchStateGuard.suspend();
|
||||
try {
|
||||
final boolean combined = !secondLayer && HeldItemGlint.begin();
|
||||
// Depth selects the surface, stencil limits it to one blend.
|
||||
// The base draw's stencil reset or the first layer's marks avoid a masked full-screen clear.
|
||||
GLStateManager.glPushAttrib(GL11.GL_STENCIL_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
try {
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
|
||||
GLStateManager.glStencilMask(1);
|
||||
if (secondLayer) {
|
||||
// Same pixels as the first layer, so removing its marks leaves the stencil reset.
|
||||
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 1);
|
||||
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_DECR);
|
||||
} else {
|
||||
if (!angelica$stencilPrepared) {
|
||||
GLStateManager.glClearStencil(0);
|
||||
GLStateManager.glClear(GL11.GL_STENCIL_BUFFER_BIT);
|
||||
}
|
||||
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 0, 1);
|
||||
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_INCR);
|
||||
}
|
||||
angelica$drawGlint(tess, iconWidth, iconHeight, thickness);
|
||||
} finally {
|
||||
GLStateManager.glPopAttrib();
|
||||
if (combined) CombinedGlint.end();
|
||||
}
|
||||
angelica$combinedGlint = combined;
|
||||
angelica$firstLayerMarked = !combined && !secondLayer && HeldItemGlint.layersCoverSamePixels();
|
||||
} finally {
|
||||
BatchStateGuard.resume();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static void angelica$drawGlint(Tessellator tess, int width, int height, float thickness) {
|
||||
angelica$drawingGlint = true;
|
||||
try {
|
||||
ItemRenderer.renderItemIn2D(tess, 0, 0, 1, 1, width, height, thickness);
|
||||
} finally {
|
||||
angelica$drawingGlint = false;
|
||||
}
|
||||
original.call(tess, minU, minV, maxU, maxV, width, height, thickness);
|
||||
}
|
||||
}
|
||||
+3
-6
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.rendering.DeferredEntityOverlay;
|
||||
import net.minecraft.client.renderer.RenderGlobal;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -18,13 +19,9 @@ public class MixinRenderGlobal_DeferredEntityOverlay {
|
||||
DeferredEntityOverlay.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render deferred entity overlays after all entities have been drawn,
|
||||
* so additive-blended overlays composite correctly without depth issues.
|
||||
*/
|
||||
@Inject(method = "renderEntities", at = @At(value = "INVOKE",
|
||||
@Surround(method = "renderEntities", at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/RenderHelper;enableStandardItemLighting()V"))
|
||||
private void angelica$renderDeferredOverlays(CallbackInfo ci) {
|
||||
private void angelica$renderDeferredOverlays() {
|
||||
DeferredEntityOverlay.renderAll();
|
||||
}
|
||||
}
|
||||
+12
-17
@@ -1,19 +1,18 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.helpers.RendererLivingEntityHelper;
|
||||
import net.minecraft.client.renderer.entity.RendererLivingEntity;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Slice;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(RendererLivingEntity.class)
|
||||
public class MixinRendererLivingEntity_EyeDepth {
|
||||
|
||||
@Inject(
|
||||
@Surround(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V"),
|
||||
slice = @Slice(
|
||||
@@ -21,22 +20,18 @@ public class MixinRendererLivingEntity_EyeDepth {
|
||||
to = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V")
|
||||
)
|
||||
)
|
||||
private void angelica$eyePassPolygonOffset(CallbackInfo ci) {
|
||||
if (!RendererLivingEntityHelper.hasEyePass(this)) return;
|
||||
GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(-1.0f, -1.0f);
|
||||
private void angelica$eyePassPolygonOffset() {
|
||||
@Surround.Carry
|
||||
boolean eyes = RendererLivingEntityHelper.hasEyePass(this);
|
||||
if (eyes) {
|
||||
GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(-1.0f, -1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", shift = At.Shift.AFTER),
|
||||
slice = @Slice(
|
||||
from = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;shouldRenderPass(Lnet/minecraft/entity/EntityLivingBase;IF)I"),
|
||||
to = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V")
|
||||
)
|
||||
)
|
||||
private void angelica$eyePassPolygonOffsetEnd(CallbackInfo ci) {
|
||||
if (!RendererLivingEntityHelper.hasEyePass(this)) return;
|
||||
@Surround.Finally
|
||||
private void angelica$eyePassPolygonOffsetEnd(@Surround.Carry boolean eyes) {
|
||||
if (!eyes) return;
|
||||
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
+17
-29
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import net.coderbot.iris.Iris;
|
||||
@@ -31,7 +32,6 @@ public class MixinRendererLivingEntity_OverlayTint {
|
||||
@Unique private boolean angelica$skipReRender;
|
||||
@Unique private boolean angelica$ffpOverlayActive;
|
||||
@Unique private boolean angelica$shaderEntityColorActive;
|
||||
@Unique private boolean angelica$untint;
|
||||
|
||||
@Inject(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
@@ -93,7 +93,7 @@ public class MixinRendererLivingEntity_OverlayTint {
|
||||
return !angelica$skipReRender;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
@Surround(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V"),
|
||||
@@ -103,36 +103,25 @@ public class MixinRendererLivingEntity_OverlayTint {
|
||||
to = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V")
|
||||
),
|
||||
require = 1, expect = 1
|
||||
require = 1
|
||||
)
|
||||
private void angelica$untintAdditivePass(CallbackInfo ci) {
|
||||
angelica$untint = false;
|
||||
if (!angelica$skipReRender || !angelica$isAdditivePass()) return;
|
||||
if (angelica$ffpOverlayActive) {
|
||||
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
angelica$untint = true;
|
||||
} else if (angelica$shaderEntityColorActive) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
angelica$untint = true;
|
||||
private void angelica$untintAdditivePass() {
|
||||
@Surround.Carry
|
||||
boolean untint = false;
|
||||
if (angelica$skipReRender && angelica$isAdditivePass()) {
|
||||
if (angelica$ffpOverlayActive) {
|
||||
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
untint = true;
|
||||
} else if (angelica$shaderEntityColorActive) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
untint = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V",
|
||||
shift = At.Shift.AFTER),
|
||||
slice = @Slice(
|
||||
from = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;shouldRenderPass(Lnet/minecraft/entity/EntityLivingBase;IF)I"),
|
||||
to = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V")
|
||||
),
|
||||
require = 1, expect = 1
|
||||
)
|
||||
private void angelica$retintAfterAdditivePass(CallbackInfo ci) {
|
||||
if (!angelica$untint) return;
|
||||
angelica$untint = false;
|
||||
@Surround.Finally
|
||||
private void angelica$retintAfterAdditivePass(@Surround.Carry boolean untint) {
|
||||
if (!untint) return;
|
||||
if (angelica$ffpOverlayActive) {
|
||||
GLStateManager.setOverlayColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
|
||||
} else if (angelica$shaderEntityColorActive) {
|
||||
@@ -154,7 +143,6 @@ public class MixinRendererLivingEntity_OverlayTint {
|
||||
angelica$shaderEntityColorActive = false;
|
||||
}
|
||||
angelica$skipReRender = false;
|
||||
angelica$untint = false;
|
||||
}
|
||||
|
||||
@Unique
|
||||
|
||||
+19
-12
@@ -1,12 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.chat;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ChatLineFormattedAccessor;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.gui.ChatLine;
|
||||
import net.minecraft.client.gui.GuiNewChat;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.util.StatCollector;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@@ -14,16 +11,26 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
@Mixin(GuiNewChat.class)
|
||||
public class MixinGuiNewChat {
|
||||
|
||||
@WrapOperation(
|
||||
@Surround(
|
||||
method = "drawChat",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/util/IChatComponent;getFormattedText()Ljava/lang/String;"))
|
||||
private String angelica$cacheFormattedText(IChatComponent component, Operation<String> original, @Local ChatLine line) {
|
||||
final long epoch = StatCollector.getLastTranslationUpdateTimeInMilliseconds();
|
||||
final ChatLineFormattedAccessor cache = (ChatLineFormattedAccessor) line;
|
||||
final String cached = cache.angelica$getFormatted(epoch);
|
||||
if (cached != null) return cached;
|
||||
final String formatted = original.call(component);
|
||||
cache.angelica$setFormatted(formatted, epoch);
|
||||
private void angelica$cacheFormattedText(@Surround.Local ChatLine line) {
|
||||
@Surround.Carry
|
||||
long epoch = StatCollector.getLastTranslationUpdateTimeInMilliseconds();
|
||||
@Surround.Carry
|
||||
String cached = ((ChatLineFormattedAccessor) line).angelica$getFormatted(epoch);
|
||||
@Surround.Skip
|
||||
boolean skip = cached != null;
|
||||
}
|
||||
|
||||
@Surround.Skipped
|
||||
private String angelica$cacheFormattedTextSkipped(@Surround.Carry String cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
@Surround.Return
|
||||
private String angelica$cacheFormattedTextStore(String formatted, @Surround.Local ChatLine line, @Surround.Carry long epoch) {
|
||||
((ChatLineFormattedAccessor) line).angelica$setFormatted(formatted, epoch);
|
||||
return formatted;
|
||||
}
|
||||
}
|
||||
+11
-7
@@ -1,8 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.loading.AngelicaClientTweaker;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import cpw.mods.fml.client.SplashProgress;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@@ -17,12 +16,17 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
@SuppressWarnings("deprecation")
|
||||
@Mixin(value = SplashProgress.class, remap = false)
|
||||
public class MixinSplashProgress {
|
||||
@WrapOperation(method="Lcpw/mods/fml/client/SplashProgress;start()V", at=@At(value="INVOKE", target="Lcpw/mods/fml/client/SplashProgress;getBool(Ljava/lang/String;Z)Z"))
|
||||
private static boolean angelica$disableSplashProgress(String name, boolean def, Operation<Boolean> original) {
|
||||
if (name.equals("enabled")) {
|
||||
@Surround(method="Lcpw/mods/fml/client/SplashProgress;start()V", at=@At(value="INVOKE", target="Lcpw/mods/fml/client/SplashProgress;getBool(Ljava/lang/String;Z)Z"))
|
||||
private static void angelica$disableSplashProgress(String name) {
|
||||
@Surround.Skip
|
||||
boolean skip = name.equals("enabled");
|
||||
if (skip) {
|
||||
AngelicaClientTweaker.LOGGER.info("Disabling splash screen due to LWJGL debug mode");
|
||||
return false;
|
||||
}
|
||||
return original.call(name, def);
|
||||
}
|
||||
|
||||
@Surround.Skipped
|
||||
private static boolean angelica$disableSplashProgressSkipped() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -1,8 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.dynamiclights;
|
||||
|
||||
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
import net.minecraft.client.renderer.ItemRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -11,9 +10,11 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
@Mixin(ItemRenderer.class)
|
||||
public class MixinItemRenderer {
|
||||
|
||||
@WrapOperation(method = "renderItemInFirstPerson", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/WorldClient;getLightBrightnessForSkyBlocks(IIII)I"))
|
||||
private int angelica$dynamiclights_renderItemInFirstPerson(WorldClient theWorld, int posX, int posY, int posZ, int p_72802_4_, Operation<Integer> original){
|
||||
int lightmap = original.call(theWorld, posX, posY, posZ, p_72802_4_);
|
||||
@Surround(method = "renderItemInFirstPerson", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/WorldClient;getLightBrightnessForSkyBlocks(IIII)I"))
|
||||
private void angelica$dynamiclights_renderItemInFirstPerson() {}
|
||||
|
||||
@Surround.Return
|
||||
private int angelica$dynamiclights_renderItemInFirstPersonResult(int lightmap, WorldClient theWorld, int posX, int posY, int posZ){
|
||||
if (DynamicLights.isEnabled()) {
|
||||
final DynamicLights dl = DynamicLights.get();
|
||||
if (dl.hasLightSources()) {
|
||||
|
||||
+69
-21
@@ -1,14 +1,13 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.entity;
|
||||
|
||||
import com.gtnewhorizons.angelica.rendering.SkippedGlintBlock;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.rendering.GlintClock;
|
||||
import com.gtnewhorizons.angelica.rendering.SkippedGlintBlock;
|
||||
import com.gtnewhorizons.angelica.rendering.items.DroppedItemInstancer;
|
||||
import com.gtnewhorizons.angelica.rendering.items.HeldItemGlint;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.EntityMaterials;
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.renderer.ItemRenderer;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
@@ -46,15 +45,32 @@ public abstract class MixinItemRenderer_Instanced {
|
||||
if (stack != angelica$deferredGlintStack || pass <= angelica$deferredGlintPass) angelica$deferredGlintStack = null;
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
@Surround(
|
||||
method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V",
|
||||
ordinal = 0
|
||||
)
|
||||
ordinal = 0,
|
||||
remap = true
|
||||
),
|
||||
id = "icon",
|
||||
remap = false
|
||||
)
|
||||
private void angelica$instanceHeldIcon(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
|
||||
private void angelica$instanceHeldIcon(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness) {
|
||||
@Surround.Carry
|
||||
final long part = angelica$heldIcon(maxU, minV, minU, maxV, width, height, thickness);
|
||||
@Surround.Skip
|
||||
final boolean queued = part == DroppedItemInstancer.SKIP;
|
||||
}
|
||||
|
||||
@Surround.Finally("icon")
|
||||
private void angelica$endHeldIcon(@Surround.Carry long part) {
|
||||
DroppedItemInstancer.endPart(part);
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static long angelica$heldIcon(float maxU, float minV, float minU, float maxV, int width, int height, float thickness) {
|
||||
angelica$iconWidth = width;
|
||||
angelica$iconHeight = height;
|
||||
final ItemStack stack = angelica$stack;
|
||||
@@ -62,9 +78,10 @@ public abstract class MixinItemRenderer_Instanced {
|
||||
try {
|
||||
// A later pass without glint covers the deferred glint of an earlier one, as vanilla draws it after that glint.
|
||||
final boolean afterGlint = angelica$deferredGlintStack != null && stack == angelica$deferredGlintStack && !stack.hasEffect(angelica$pass);
|
||||
DroppedItemInstancer.icon(angelica$batchable(angelica$type) && !HeldItemGlint.needsImmediateBase(stack, angelica$pass) ? stack : null, afterGlint, tessellator, maxU, minV, minU, maxV, width, height, thickness, original);
|
||||
} finally {
|
||||
return DroppedItemInstancer.icon(angelica$batchable(angelica$type) && !HeldItemGlint.needsImmediateBase(stack, angelica$pass) ? stack : null, afterGlint, maxU, minV, minU, maxV, width, height, thickness);
|
||||
} catch (Throwable t) {
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,39 +98,69 @@ public abstract class MixinItemRenderer_Instanced {
|
||||
return false;
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
@Surround(
|
||||
method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V",
|
||||
at = {
|
||||
@At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V",
|
||||
ordinal = 1
|
||||
ordinal = 1,
|
||||
remap = true
|
||||
),
|
||||
@At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V",
|
||||
ordinal = 2
|
||||
ordinal = 2,
|
||||
remap = true
|
||||
)
|
||||
}
|
||||
},
|
||||
id = "glint",
|
||||
remap = false
|
||||
)
|
||||
private void angelica$instanceHeldGlint(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
|
||||
private void angelica$instanceHeldGlint(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness) {
|
||||
@Surround.Carry
|
||||
final long part = angelica$heldGlint(maxU, minV, minU, maxV, thickness);
|
||||
@Surround.Skip
|
||||
final boolean queued = part == DroppedItemInstancer.SKIP;
|
||||
}
|
||||
|
||||
@Surround.Finally("glint")
|
||||
private void angelica$endHeldGlint(@Surround.Carry long part) {
|
||||
DroppedItemInstancer.endPart(part);
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static long angelica$heldGlint(float maxU, float minV, float minU, float maxV, float thickness) {
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(angelica$Z_HELD_GLINT);
|
||||
try {
|
||||
DroppedItemInstancer.glint(tessellator, maxU, minV, minU, maxV, angelica$iconWidth, angelica$iconHeight, thickness, EntityMaterials.ITEM_GLINT, original);
|
||||
} finally {
|
||||
return DroppedItemInstancer.glint(maxU, minV, minU, maxV, angelica$iconWidth, angelica$iconHeight, thickness, EntityMaterials.ITEM_GLINT);
|
||||
} catch (Throwable t) {
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
@Surround(
|
||||
method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/RenderBlocks;renderBlockAsItem(Lnet/minecraft/block/Block;IF)V"
|
||||
)
|
||||
target = "Lnet/minecraft/client/renderer/RenderBlocks;renderBlockAsItem(Lnet/minecraft/block/Block;IF)V",
|
||||
remap = true
|
||||
),
|
||||
id = "block",
|
||||
remap = false
|
||||
)
|
||||
private void angelica$instanceHeldBlock(RenderBlocks renderBlocks, Block block, int meta, float brightness, Operation<Void> original) {
|
||||
DroppedItemInstancer.block(angelica$batchable(angelica$type) ? angelica$stack : null, renderBlocks, block, meta, brightness, false, original);
|
||||
private void angelica$instanceHeldBlock(RenderBlocks renderBlocks, Block block, int meta, float brightness) {
|
||||
@Surround.Carry
|
||||
final long part = DroppedItemInstancer.block(angelica$batchable(angelica$type) ? angelica$stack : null, renderBlocks, block, meta, brightness, false);
|
||||
@Surround.Skip
|
||||
final boolean queued = part == DroppedItemInstancer.SKIP;
|
||||
}
|
||||
|
||||
@Surround.Finally("block")
|
||||
private void angelica$endHeldBlock(@Surround.Carry long part) {
|
||||
DroppedItemInstancer.endPart(part);
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
@@ -124,6 +171,7 @@ public abstract class MixinItemRenderer_Instanced {
|
||||
return GlintClock.millis();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean angelica$batchable(ItemRenderType type) {
|
||||
return type == ItemRenderType.EQUIPPED || type == ItemRenderType.ENTITY;
|
||||
}
|
||||
|
||||
+54
-34
@@ -1,13 +1,11 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.entity;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.GlintClock;
|
||||
import com.gtnewhorizons.angelica.rendering.items.DroppedItemInstancer;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.EntityMaterials;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
@@ -15,42 +13,63 @@ import net.minecraft.client.renderer.entity.RenderItem;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(RenderItem.class)
|
||||
public abstract class MixinRenderItem_Instanced {
|
||||
|
||||
@WrapOperation(
|
||||
@Surround(
|
||||
method = "renderDroppedItem(Lnet/minecraft/entity/item/EntityItem;Lnet/minecraft/util/IIcon;IFFFFI)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V",
|
||||
ordinal = 0
|
||||
)
|
||||
ordinal = 0,
|
||||
remap = true
|
||||
),
|
||||
id = "icon",
|
||||
remap = false
|
||||
)
|
||||
private void angelica$instanceDroppedIcon(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original, @Local ItemStack stack) {
|
||||
DroppedItemInstancer.icon(stack, false, tessellator, maxU, minV, minU, maxV, width, height, thickness, original);
|
||||
private void angelica$instanceDroppedIcon(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, @Surround.Local ItemStack stack) {
|
||||
@Surround.Carry
|
||||
final long part = DroppedItemInstancer.icon(stack, false, maxU, minV, minU, maxV, width, height, thickness);
|
||||
@Surround.Skip
|
||||
final boolean queued = part == DroppedItemInstancer.SKIP;
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
@Surround.Finally("icon")
|
||||
private void angelica$endDroppedIcon(@Surround.Carry long part) {
|
||||
DroppedItemInstancer.endPart(part);
|
||||
}
|
||||
|
||||
@Surround(
|
||||
method = "renderDroppedItem(Lnet/minecraft/entity/item/EntityItem;Lnet/minecraft/util/IIcon;IFFFFI)V",
|
||||
at = {
|
||||
@At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V",
|
||||
ordinal = 1
|
||||
ordinal = 1,
|
||||
remap = true
|
||||
),
|
||||
@At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V",
|
||||
ordinal = 2
|
||||
ordinal = 2,
|
||||
remap = true
|
||||
)
|
||||
}
|
||||
},
|
||||
id = "glint",
|
||||
remap = false
|
||||
)
|
||||
private void angelica$instanceDroppedGlint(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
|
||||
DroppedItemInstancer.glint(tessellator, maxU, minV, minU, maxV, width, height, thickness, EntityMaterials.GLINT, original);
|
||||
private void angelica$instanceDroppedGlint(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness) {
|
||||
@Surround.Carry
|
||||
final long part = DroppedItemInstancer.glint(maxU, minV, minU, maxV, width, height, thickness, EntityMaterials.GLINT);
|
||||
@Surround.Skip
|
||||
final boolean queued = part == DroppedItemInstancer.SKIP;
|
||||
}
|
||||
|
||||
@Surround.Finally("glint")
|
||||
private void angelica$endDroppedGlint(@Surround.Carry long part) {
|
||||
DroppedItemInstancer.endPart(part);
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
@@ -61,41 +80,42 @@ public abstract class MixinRenderItem_Instanced {
|
||||
return GlintClock.millis();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
@Surround(
|
||||
method = "doRender(Lnet/minecraft/entity/item/EntityItem;DDDFF)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraftforge/client/ForgeHooksClient;renderEntityItem(Lnet/minecraft/entity/item/EntityItem;Lnet/minecraft/item/ItemStack;"
|
||||
+ "FFLjava/util/Random;Lnet/minecraft/client/renderer/texture/TextureManager;Lnet/minecraft/client/renderer/RenderBlocks;I)Z",
|
||||
remap = false
|
||||
)
|
||||
),
|
||||
id = "customDraws"
|
||||
)
|
||||
private void angelica$beginCustomItemDraws(CallbackInfo ci) {
|
||||
private void angelica$beginCustomItemDraws() {
|
||||
BatchEligibility.beginExpectedDraws(GLStateManager.drawCalls);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "doRender(Lnet/minecraft/entity/item/EntityItem;DDDFF)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraftforge/client/ForgeHooksClient;renderEntityItem(Lnet/minecraft/entity/item/EntityItem;Lnet/minecraft/item/ItemStack;"
|
||||
+ "FFLjava/util/Random;Lnet/minecraft/client/renderer/texture/TextureManager;Lnet/minecraft/client/renderer/RenderBlocks;I)Z",
|
||||
shift = At.Shift.AFTER,
|
||||
remap = false
|
||||
)
|
||||
)
|
||||
private void angelica$endCustomItemDraws(CallbackInfo ci) {
|
||||
@Surround.Finally("customDraws")
|
||||
private void angelica$endCustomItemDraws() {
|
||||
BatchEligibility.endExpectedDraws(GLStateManager.drawCalls);
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
@Surround(
|
||||
method = "doRender(Lnet/minecraft/entity/item/EntityItem;DDDFF)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/RenderBlocks;renderBlockAsItem(Lnet/minecraft/block/Block;IF)V"
|
||||
)
|
||||
),
|
||||
id = "block"
|
||||
)
|
||||
private void angelica$instanceDroppedBlock(RenderBlocks renderBlocks, Block block, int meta, float brightness, Operation<Void> original, @Local ItemStack stack) {
|
||||
DroppedItemInstancer.block(stack, renderBlocks, block, meta, brightness, true, original);
|
||||
private void angelica$instanceDroppedBlock(RenderBlocks renderBlocks, Block block, int meta, float brightness, @Surround.Local ItemStack stack) {
|
||||
@Surround.Carry
|
||||
final long part = DroppedItemInstancer.block(stack, renderBlocks, block, meta, brightness, true);
|
||||
@Surround.Skip
|
||||
final boolean queued = part == DroppedItemInstancer.SKIP;
|
||||
}
|
||||
|
||||
@Surround.Finally("block")
|
||||
private void angelica$endDroppedBlock(@Surround.Carry long part) {
|
||||
DroppedItemInstancer.endPart(part);
|
||||
}
|
||||
}
|
||||
+5
-9
@@ -1,29 +1,25 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.entity;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
|
||||
import net.minecraft.client.renderer.entity.RendererLivingEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(RendererLivingEntity.class)
|
||||
public abstract class MixinRendererLivingEntity_EquippedDraws {
|
||||
|
||||
@Inject(
|
||||
@Surround(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V")
|
||||
)
|
||||
private void angelica$beginEquippedDraws(CallbackInfo ci) {
|
||||
private void angelica$countEquippedDraws() {
|
||||
BatchEligibility.beginExpectedDraws(GLStateManager.drawCalls);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V", shift = At.Shift.AFTER)
|
||||
)
|
||||
private void angelica$endEquippedDraws(CallbackInfo ci) {
|
||||
@Surround.Finally
|
||||
private void angelica$endEquippedDraws() {
|
||||
BatchEligibility.endExpectedDraws(GLStateManager.drawCalls);
|
||||
}
|
||||
}
|
||||
+5
-9
@@ -1,29 +1,25 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.entity;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
|
||||
import net.minecraft.client.renderer.entity.RendererLivingEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(RendererLivingEntity.class)
|
||||
public abstract class MixinRendererLivingEntity_ModelPassDraws {
|
||||
|
||||
@Inject(
|
||||
@Surround(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V")
|
||||
)
|
||||
private void angelica$beginModelPassDraws(CallbackInfo ci) {
|
||||
private void angelica$countModelPassDraws() {
|
||||
BatchEligibility.beginExpectedDraws(GLStateManager.drawCalls);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", shift = At.Shift.AFTER)
|
||||
)
|
||||
private void angelica$endModelPassDraws(CallbackInfo ci) {
|
||||
@Surround.Finally
|
||||
private void angelica$endModelPassDraws() {
|
||||
BatchEligibility.endExpectedDraws(GLStateManager.drawCalls);
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.fontrenderer;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
@@ -42,14 +43,14 @@ public class MixinGuiIngameForge {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "renderHotbar", at = @At("HEAD"), remap = false)
|
||||
private void angelica$startHotbarTextBatching(int width, int height, float partialTicks, CallbackInfo ci) {
|
||||
@Surround(method = "renderHotbar", remap = false)
|
||||
private void angelica$startHotbarTextBatching(int width, int height, float partialTicks) {
|
||||
final FontRendererAccessor fra = (FontRendererAccessor) (Object) fontrenderer;
|
||||
fra.angelica$getBatcher().beginBatch();
|
||||
}
|
||||
|
||||
@Inject(method = "renderHotbar", at = @At("RETURN"), remap = false)
|
||||
private void angelica$endHotbarTextBatching(int width, int height, float partialTicks, CallbackInfo ci) {
|
||||
@Surround.Finally
|
||||
private void angelica$endHotbarTextBatching() {
|
||||
final FontRendererAccessor fra = (FontRendererAccessor) (Object) fontrenderer;
|
||||
|
||||
final boolean depthWasEnabled = GLStateManager.getDepthTest().isEnabled();
|
||||
|
||||
+5
-12
@@ -3,13 +3,11 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.hudcaching;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.gui.GuiIngame;
|
||||
|
||||
@Mixin(GuiIngame.class)
|
||||
@@ -48,14 +46,9 @@ public class MixinGuiIngame_HUDCaching {
|
||||
}
|
||||
}
|
||||
|
||||
@WrapOperation(method = "func_96136_a", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/FontRenderer;drawString(Ljava/lang/String;III)I"))
|
||||
private int angelica$fixScoreboardTextAlpha(FontRenderer fontRenderer, String text, int x, int y, int color, Operation<Integer> op) {
|
||||
// Vanilla uses 0x20 for the alpha but it looks like 0xFF for some reason
|
||||
// here we just make it 0xFF so it doesn't cause issues
|
||||
if (HUDCaching.renderingCacheOverride) {
|
||||
color |= 0xFF000000;
|
||||
}
|
||||
return op.call(fontRenderer, text, x, y, color);
|
||||
// Vanilla uses 0x20 for the alpha but it looks like 0xFF for some reason
|
||||
@ModifyArg(method = "func_96136_a", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/FontRenderer;drawString(Ljava/lang/String;III)I"), index = 3)
|
||||
private int angelica$fixScoreboardTextAlpha(int color) {
|
||||
return HUDCaching.renderingCacheOverride ? color | 0xFF000000 : color;
|
||||
}
|
||||
|
||||
}
|
||||
+12
-13
@@ -27,10 +27,9 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.itemrenderer;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.items.ItemRenderListManager;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import net.minecraft.client.renderer.ItemRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@@ -42,17 +41,17 @@ import net.minecraft.client.renderer.Tessellator;
|
||||
@Mixin(ItemRenderer.class)
|
||||
public abstract class MixinItemRenderer {
|
||||
|
||||
@WrapMethod(method = "renderItemIn2D")
|
||||
private static void angelica$cacheItem(Tessellator tessellator, float minU, float minV, float maxU, float maxV, int widthSubdivisions, int heightSubdivisions, float thickness, Operation<Void> original) {
|
||||
if (GLStateManager.isRecordingDisplayList() || TessellatorManager.isCurrentlyCapturing() || TessellatorManager.shouldInterceptDraw(Tessellator.instance)) {
|
||||
original.call(tessellator, minU, minV, maxU, maxV, widthSubdivisions, heightSubdivisions, thickness);
|
||||
return;
|
||||
}
|
||||
final ItemRenderListManager.CachedVBO vbo = ItemRenderListManager.pre(minU, minV, maxU, maxV, widthSubdivisions, heightSubdivisions, thickness);
|
||||
if (vbo != null) {
|
||||
final DirectTessellator tess = TessellatorManager.startCapturingDirect();
|
||||
original.call(tess, minU, minV, maxU, maxV, widthSubdivisions, heightSubdivisions, thickness);
|
||||
ItemRenderListManager.post(tess, vbo);
|
||||
@Surround(method = "renderItemIn2D")
|
||||
private static void angelica$cacheItem(Tessellator tessellator, float minU, float minV, float maxU, float maxV, int widthSubdivisions, int heightSubdivisions, float thickness) {
|
||||
@Surround.Skip
|
||||
final boolean cached = !(GLStateManager.isRecordingDisplayList() || TessellatorManager.isCurrentlyCapturing() || TessellatorManager.shouldInterceptDraw(Tessellator.instance));
|
||||
if (cached) {
|
||||
final ItemRenderListManager.CachedVBO vbo = ItemRenderListManager.pre(minU, minV, maxU, maxV, widthSubdivisions, heightSubdivisions, thickness);
|
||||
if (vbo != null) {
|
||||
final DirectTessellator tess = TessellatorManager.startCapturingDirect();
|
||||
ItemRenderer.renderItemIn2D(tess, minU, minV, maxU, maxV, widthSubdivisions, heightSubdivisions, thickness);
|
||||
ItemRenderListManager.post(tess, vbo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-19
@@ -1,10 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.itemrenderer;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.items.BlockRenderListManager;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
@@ -13,7 +12,8 @@ import net.minecraft.world.IBlockAccess;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(RenderBlocks.class)
|
||||
// Innermost: shaders.MixinRenderBlocks_ItemId must also cover a cache hit
|
||||
@Mixin(value = RenderBlocks.class, priority = 900)
|
||||
public abstract class MixinRenderBlocks {
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ public abstract class MixinRenderBlocks {
|
||||
@Shadow public int uvRotateTop;
|
||||
@Shadow public int uvRotateBottom;
|
||||
|
||||
@WrapMethod(method = "renderBlockAsItem")
|
||||
private void angelica$cacheBlockItemRenderer(Block block, int meta, float brightness, Operation<Void> original) {
|
||||
if (BlockRenderListManager.isISBRH(block.getRenderType())
|
||||
@Surround(method = "renderBlockAsItem")
|
||||
private void angelica$cacheBlockItemRenderer(Block block, int meta, float brightness) {
|
||||
final boolean uncacheable = BlockRenderListManager.isISBRH(block.getRenderType())
|
||||
|| enableAO
|
||||
|| this.overrideBlockTexture != null
|
||||
|| brightness != 1.0F
|
||||
@@ -46,18 +46,20 @@ public abstract class MixinRenderBlocks {
|
||||
|| (uvRotateEast | uvRotateWest | uvRotateSouth | uvRotateNorth | uvRotateTop | uvRotateBottom) != 0
|
||||
|| GLStateManager.isRecordingDisplayList()
|
||||
|| TessellatorManager.isCurrentlyCapturing()
|
||||
|| TessellatorManager.shouldInterceptDraw(Tessellator.instance)
|
||||
) {
|
||||
// Do not cache those
|
||||
original.call(block, meta, brightness);
|
||||
return;
|
||||
}
|
||||
int list = BlockRenderListManager.getDisplayList(block, meta);
|
||||
if (list == 0) {
|
||||
list = BlockRenderListManager.startCompiling();
|
||||
original.call(block, meta, brightness);
|
||||
BlockRenderListManager.endCompiling(list, block, meta);
|
||||
}
|
||||
GLStateManager.glCallList(list);
|
||||
|| TessellatorManager.shouldInterceptDraw(Tessellator.instance);
|
||||
@Surround.Carry
|
||||
final int list = uncacheable ? 0 : BlockRenderListManager.callOrStartCompiling(BlockRenderListManager.getDisplayList(block, meta));
|
||||
@Surround.Skip
|
||||
final boolean cached = list < 0;
|
||||
}
|
||||
|
||||
@Surround.Return
|
||||
private void angelica$endBlockItemRenderer(Block block, int meta, float brightness, @Surround.Carry int list) {
|
||||
BlockRenderListManager.endCompiling(list, block, meta);
|
||||
}
|
||||
|
||||
@Surround.Catch
|
||||
private void angelica$abortBlockItemRenderer(Throwable error, @Surround.Carry int list) {
|
||||
BlockRenderListManager.abortCompiling(list);
|
||||
}
|
||||
}
|
||||
+44
-21
@@ -1,10 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.itemrenderer;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.items.BlockRenderListManager;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.tileentity.RenderItemFrame;
|
||||
import net.minecraft.entity.item.EntityItemFrame;
|
||||
@@ -15,42 +14,66 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
@Mixin(RenderItemFrame.class)
|
||||
public class MixinRenderItemFrame {
|
||||
|
||||
@WrapOperation(
|
||||
@Surround(
|
||||
method = "doRender(Lnet/minecraft/entity/item/EntityItemFrame;DDDFF)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/tileentity/RenderItemFrame;renderFrameItemAsBlock(Lnet/minecraft/entity/item/EntityItemFrame;)V"
|
||||
)
|
||||
),
|
||||
id = "frame"
|
||||
)
|
||||
private void angelica$cacheFrame(RenderItemFrame renderer, EntityItemFrame frame, Operation<Void> original) {
|
||||
angelica$renderCached(renderer, frame, original, frame.hangingDirection);
|
||||
private void angelica$cacheFrame(RenderItemFrame renderer, EntityItemFrame frame) {
|
||||
@Surround.Carry("key")
|
||||
final int key = frame.hangingDirection;
|
||||
@Surround.Carry("list")
|
||||
final int list = angelica$beginCached(key);
|
||||
@Surround.Skip
|
||||
final boolean cached = list < 0;
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
@Surround.Return("frame")
|
||||
private void angelica$endFrame(@Surround.Carry("key") int key, @Surround.Carry("list") int list) {
|
||||
BlockRenderListManager.endItemFrameCompiling(list, key);
|
||||
}
|
||||
|
||||
@Surround.Catch("frame")
|
||||
private void angelica$abortFrame(Throwable error, @Surround.Carry("list") int list) {
|
||||
BlockRenderListManager.abortCompiling(list);
|
||||
}
|
||||
|
||||
@Surround(
|
||||
method = "doRender(Lnet/minecraft/entity/item/EntityItemFrame;DDDFF)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/tileentity/RenderItemFrame;func_147915_b(Lnet/minecraft/entity/item/EntityItemFrame;)V"
|
||||
)
|
||||
),
|
||||
id = "map"
|
||||
)
|
||||
private void angelica$cacheMapFrame(RenderItemFrame renderer, EntityItemFrame frame, Operation<Void> original) {
|
||||
angelica$renderCached(renderer, frame, original, 4 + frame.hangingDirection);
|
||||
private void angelica$cacheMapFrame(RenderItemFrame renderer, EntityItemFrame frame) {
|
||||
@Surround.Carry("key")
|
||||
final int key = 4 + frame.hangingDirection;
|
||||
@Surround.Carry("list")
|
||||
final int list = angelica$beginCached(key);
|
||||
@Surround.Skip
|
||||
final boolean cached = list < 0;
|
||||
}
|
||||
|
||||
@Surround.Return("map")
|
||||
private void angelica$endMapFrame(@Surround.Carry("key") int key, @Surround.Carry("list") int list) {
|
||||
BlockRenderListManager.endItemFrameCompiling(list, key);
|
||||
}
|
||||
|
||||
@Surround.Catch("map")
|
||||
private void angelica$abortMapFrame(Throwable error, @Surround.Carry("list") int list) {
|
||||
BlockRenderListManager.abortCompiling(list);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static void angelica$renderCached(RenderItemFrame renderer, EntityItemFrame frame,
|
||||
Operation<Void> original, int keyIndex) {
|
||||
private static int angelica$beginCached(int keyIndex) {
|
||||
if (GLStateManager.isRecordingDisplayList() || TessellatorManager.isCurrentlyCapturing()
|
||||
|| TessellatorManager.shouldInterceptDraw(Tessellator.instance)) {
|
||||
original.call(renderer, frame);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
int list = BlockRenderListManager.getItemFrameDisplayList(keyIndex);
|
||||
if (list == 0) {
|
||||
list = BlockRenderListManager.startCompiling();
|
||||
original.call(renderer, frame);
|
||||
BlockRenderListManager.endItemFrameCompiling(list, keyIndex);
|
||||
}
|
||||
GLStateManager.glCallList(list);
|
||||
return BlockRenderListManager.callOrStartCompiling(BlockRenderListManager.getItemFrameDisplayList(keyIndex));
|
||||
}
|
||||
}
|
||||
+12
-11
@@ -1,16 +1,13 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.lighting;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalIntRef;
|
||||
import net.minecraft.world.EnumSkyBlock;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import net.minecraft.world.World;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Constant;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/**
|
||||
* This mixin is a backport of a Forge fix https://github.com/MinecraftForge/MinecraftForge/pull/4729
|
||||
@@ -21,19 +18,23 @@ public abstract class MixinWorld_FixLightUpdateLag {
|
||||
@Shadow
|
||||
public abstract boolean doChunksNearChunkExist(int p_72873_1_, int p_72873_2_, int p_72873_3_, int p_72873_4_);
|
||||
|
||||
@Unique
|
||||
private int angelica$updateRange;
|
||||
|
||||
@ModifyConstant(method = "updateLightByType", constant = @Constant(intValue = 17, ordinal = 0))
|
||||
public int angelica$modifyRangeCheck1(int cst) {
|
||||
return 16;
|
||||
}
|
||||
|
||||
@Inject(method = "updateLightByType", at = @At(value = "FIELD", target = "Lnet/minecraft/world/World;theProfiler:Lnet/minecraft/profiler/Profiler;", shift = At.Shift.BEFORE, ordinal = 0))
|
||||
public void angelica$modifyUpdateRange(EnumSkyBlock p_147463_1_, int x, int y, int z,
|
||||
CallbackInfoReturnable<Boolean> cir, @Share("updateRange") LocalIntRef updateRange) {
|
||||
updateRange.set(this.doChunksNearChunkExist(x, y, z, 18) ? 17 : 15);
|
||||
@Surround(method = "updateLightByType", id = "range",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/profiler/Profiler;startSection(Ljava/lang/String;)V", ordinal = 0))
|
||||
private void angelica$modifyUpdateRange(@Surround.Local(argsOnly = true, ordinal = 0) int x, @Surround.Local(argsOnly = true, ordinal = 1) int y,
|
||||
@Surround.Local(argsOnly = true, ordinal = 2) int z) {
|
||||
this.angelica$updateRange = this.doChunksNearChunkExist(x, y, z, 18) ? 17 : 15;
|
||||
}
|
||||
|
||||
@ModifyConstant(method = "updateLightByType", constant = { @Constant(intValue = 17, ordinal = 1), @Constant(intValue = 17, ordinal = 2) })
|
||||
public int angelica$modifyRangeCheck2(int cst, @Share("updateRange") LocalIntRef updateRange) {
|
||||
return updateRange.get();
|
||||
public int angelica$modifyRangeCheck2(int cst) {
|
||||
return this.angelica$updateRange;
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -1,8 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.particles;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.rendering.particles.ParticleInstancer;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.client.particle.EffectRenderer;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
@@ -15,11 +14,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
@Mixin(value = EffectRenderer.class, priority = 900)
|
||||
public class MixinEffectRenderer_ParticleBatch {
|
||||
|
||||
@WrapOperation(method = "renderParticles",
|
||||
@Surround(method = "renderParticles",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/particle/EntityFX;renderParticle(Lnet/minecraft/client/renderer/Tessellator;FFFFFF)V"))
|
||||
private void angelica$captureParticle(EntityFX particle, Tessellator tessellator, float partialTicks, float rotationX, float rotationXZ, float rotationZ, float rotationYZ, float rotationXY, Operation<Void> original) {
|
||||
ParticleInstancer.renderParticle(particle, tessellator, partialTicks, rotationX, rotationXZ, rotationZ, rotationYZ, rotationXY, original);
|
||||
private void angelica$captureParticle(EntityFX particle, Tessellator tessellator, float partialTicks, float rotationX, float rotationXZ, float rotationZ, float rotationYZ, float rotationXY) {
|
||||
@Surround.Skip
|
||||
final boolean batched = ParticleInstancer.renderParticle(particle, tessellator, partialTicks, rotationX, rotationXZ, rotationZ, rotationYZ, rotationXY);
|
||||
}
|
||||
|
||||
@Inject(method = "renderParticles",
|
||||
|
||||
+11
-16
@@ -1,11 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.tesr;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.VanillaModelMeshes;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.model.ModelChest;
|
||||
import net.minecraft.client.model.ModelLargeChest;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntityChestRenderer;
|
||||
@@ -15,6 +12,8 @@ import org.spongepowered.asm.mixin.Dynamic;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(TileEntityChestRenderer.class)
|
||||
public abstract class MixinTileEntityChestRenderer {
|
||||
@@ -22,21 +21,17 @@ public abstract class MixinTileEntityChestRenderer {
|
||||
private ResourceLocation angelica$lastBoundTexture = null;
|
||||
|
||||
@Dynamic(mixin = MixinTileEntityChestRenderer_BindTexture.class)
|
||||
@WrapMethod(method = "bindTexture(Lnet/minecraft/util/ResourceLocation;)V", require = 1)
|
||||
private void angelica$storeBoundTexture(ResourceLocation resourceLocation, Operation<Void> original) {
|
||||
@Inject(method = "bindTexture(Lnet/minecraft/util/ResourceLocation;)V", at = @At("HEAD"), require = 1)
|
||||
private void angelica$storeBoundTexture(ResourceLocation resourceLocation, CallbackInfo ci) {
|
||||
this.angelica$lastBoundTexture = resourceLocation;
|
||||
original.call(resourceLocation);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntityChest;DDDF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelChest;renderAll()V"))
|
||||
private void angelica$cachedRenderAll(ModelChest model, Operation<Void> original, @Local(argsOnly = true) TileEntityChest chest) {
|
||||
if (!AngelicaConfig.enableTESRChestCache || chest.getWorldObj() == null) {
|
||||
original.call(model);
|
||||
return;
|
||||
}
|
||||
final boolean isDouble = model instanceof ModelLargeChest;
|
||||
if (angelica$lastBoundTexture != null) {
|
||||
VanillaModelMeshes.renderChest(model, angelica$lastBoundTexture, isDouble);
|
||||
@Surround(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntityChest;DDDF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelChest;renderAll()V"))
|
||||
private void angelica$cachedRenderAll(ModelChest model, @Surround.Local(argsOnly = true) TileEntityChest chest) {
|
||||
@Surround.Skip
|
||||
boolean skip = AngelicaConfig.enableTESRChestCache && chest.getWorldObj() != null;
|
||||
if (skip && angelica$lastBoundTexture != null) {
|
||||
VanillaModelMeshes.renderChest(model, angelica$lastBoundTexture, model instanceof ModelLargeChest);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-14
@@ -1,11 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.tesr;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.VanillaModelMeshes;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.model.ModelChest;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntityEnderChestRenderer;
|
||||
import net.minecraft.tileentity.TileEntityEnderChest;
|
||||
@@ -14,6 +11,8 @@ import org.spongepowered.asm.mixin.Dynamic;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(TileEntityEnderChestRenderer.class)
|
||||
public abstract class MixinTileEntityEnderChestRenderer {
|
||||
@@ -21,19 +20,16 @@ public abstract class MixinTileEntityEnderChestRenderer {
|
||||
private ResourceLocation angelica$lastBoundTexture = null;
|
||||
|
||||
@Dynamic(mixin = MixinTileEntityChestRenderer_BindTexture.class)
|
||||
@WrapMethod(method = "bindTexture(Lnet/minecraft/util/ResourceLocation;)V", require = 1)
|
||||
private void angelica$storeBoundTexture(ResourceLocation resourceLocation, Operation<Void> original) {
|
||||
@Inject(method = "bindTexture(Lnet/minecraft/util/ResourceLocation;)V", at = @At("HEAD"), require = 1)
|
||||
private void angelica$storeBoundTexture(ResourceLocation resourceLocation, CallbackInfo ci) {
|
||||
this.angelica$lastBoundTexture = resourceLocation;
|
||||
original.call(resourceLocation);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntityEnderChest;DDDF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelChest;renderAll()V"))
|
||||
private void angelica$cachedRenderAll(ModelChest model, Operation<Void> original, @Local(argsOnly = true) TileEntityEnderChest chest) {
|
||||
if (!AngelicaConfig.enableTESRChestCache || chest.getWorldObj() == null) {
|
||||
original.call(model);
|
||||
return;
|
||||
}
|
||||
if (angelica$lastBoundTexture != null) {
|
||||
@Surround(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntityEnderChest;DDDF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelChest;renderAll()V"))
|
||||
private void angelica$cachedRenderAll(ModelChest model, @Surround.Local(argsOnly = true) TileEntityEnderChest chest) {
|
||||
@Surround.Skip
|
||||
boolean skip = AngelicaConfig.enableTESRChestCache && chest.getWorldObj() != null;
|
||||
if (skip && angelica$lastBoundTexture != null) {
|
||||
VanillaModelMeshes.renderChest(model, angelica$lastBoundTexture, false);
|
||||
}
|
||||
}
|
||||
|
||||
+20
-17
@@ -1,9 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.textures;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.textures.atlas.AtlasLoadSession;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
import net.minecraft.client.resources.IResource;
|
||||
@@ -35,21 +35,24 @@ public abstract class MixinTextureMap_ParallelLoad {
|
||||
this.angelica$atlasSession = AtlasLoadSession.begin((TextureMap) (Object) this, manager);
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
@Surround(
|
||||
method = "loadTextureAtlas",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/resources/IResourceManager;getResource(Lnet/minecraft/util/ResourceLocation;)Lnet/minecraft/client/resources/IResource;",
|
||||
ordinal = 0))
|
||||
private IResource angelica$takePrefetchedResource(IResourceManager manager, ResourceLocation location, Operation<IResource> original, @Local(ordinal = 0) TextureAtlasSprite sprite) {
|
||||
ordinal = 0),
|
||||
id = "resource")
|
||||
private void angelica$takePrefetchedResource(IResourceManager manager, ResourceLocation location, @Surround.Local(ordinal = 0) TextureAtlasSprite sprite) {
|
||||
final AtlasLoadSession session = this.angelica$atlasSession;
|
||||
if (session != null) {
|
||||
final IResource resource = session.takeResource(manager, sprite, location);
|
||||
if (resource != null) {
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
return original.call(manager, location);
|
||||
@Surround.Carry
|
||||
final IResource resource = session == null ? null : session.takeResource(manager, sprite, location);
|
||||
@Surround.Skip
|
||||
final boolean prefetched = resource != null;
|
||||
}
|
||||
|
||||
@Surround.Skipped("resource")
|
||||
private IResource angelica$prefetchedResource(@Surround.Carry IResource resource) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
@@ -86,17 +89,17 @@ public abstract class MixinTextureMap_ParallelLoad {
|
||||
}
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
@Surround(
|
||||
method = "loadTextureAtlas",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;generateMipmaps(I)V",
|
||||
ordinal = 0))
|
||||
private void angelica$joinMipmaps(TextureAtlasSprite sprite, int levels, Operation<Void> original) {
|
||||
ordinal = 0),
|
||||
id = "mipmaps")
|
||||
private void angelica$joinMipmaps(TextureAtlasSprite sprite, int levels) {
|
||||
final AtlasLoadSession session = this.angelica$atlasSession;
|
||||
if (session == null || !session.joinMipmaps(sprite, levels)) {
|
||||
original.call(sprite, levels);
|
||||
}
|
||||
@Surround.Skip
|
||||
final boolean joined = session != null && session.joinMipmaps(sprite, levels);
|
||||
}
|
||||
|
||||
@Inject(method = "loadTextureAtlas", at = @At("RETURN"))
|
||||
|
||||
+16
-14
@@ -1,10 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.tracy;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.profiling.RenderClassTimings;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrAttribution;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.client.renderer.RenderGlobal;
|
||||
import net.minecraft.client.renderer.entity.RenderManager;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -28,9 +27,12 @@ public class MixinRenderGlobal_Tracy {
|
||||
Tracy.endZone();
|
||||
}
|
||||
|
||||
@WrapOperation(method = "renderEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RenderManager;renderEntitySimple(Lnet/minecraft/entity/Entity;F)Z"))
|
||||
private boolean angelica$timeEntityRender(RenderManager instance, Entity entity, float partialTicks, Operation<Boolean> original) {
|
||||
final long start = System.nanoTime();
|
||||
@Surround(method = "renderEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RenderManager;renderEntitySimple(Lnet/minecraft/entity/Entity;F)Z"))
|
||||
private void angelica$timeEntityRender(RenderManager instance, Entity entity) {
|
||||
@Surround.Carry
|
||||
long start = System.nanoTime();
|
||||
@Surround.Carry
|
||||
Class<?> prevRenderable = TesrAttribution.currentRenderable;
|
||||
if (TesrAttribution.currentRenderable == null && entity != null) {
|
||||
TesrAttribution.currentRenderable = entity.getClass();
|
||||
}
|
||||
@@ -38,15 +40,15 @@ public class MixinRenderGlobal_Tracy {
|
||||
Tracy.setGpuZonesEnabled(false);
|
||||
Tracy.beginZone(Z_ENTITY_RENDER);
|
||||
}
|
||||
try {
|
||||
return original.call(instance, entity, partialTicks);
|
||||
} finally {
|
||||
if (Tracy.FINE_ZONES) {
|
||||
Tracy.endZone();
|
||||
Tracy.setGpuZonesEnabled(true);
|
||||
}
|
||||
TesrAttribution.currentRenderable = null;
|
||||
RenderClassTimings.ENTITY.add(entity.getClass(), System.nanoTime() - start);
|
||||
}
|
||||
|
||||
@Surround.Finally
|
||||
private void angelica$endEntityRender(RenderManager instance, Entity entity, @Surround.Carry long start, @Surround.Carry Class<?> prevRenderable) {
|
||||
if (Tracy.FINE_ZONES) {
|
||||
Tracy.endZone();
|
||||
Tracy.setGpuZonesEnabled(true);
|
||||
}
|
||||
TesrAttribution.currentRenderable = prevRenderable;
|
||||
RenderClassTimings.ENTITY.add(entity.getClass(), System.nanoTime() - start);
|
||||
}
|
||||
}
|
||||
+13
-14
@@ -1,25 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.celeritas.biome_blending;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeVertexBlender;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.WorldClientExtension;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import net.minecraft.block.BlockGrass;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(BlockGrass.class)
|
||||
public class MixinBlockGrass {
|
||||
@WrapMethod(method = "colorMultiplier")
|
||||
private int celeritas$smoothBlendColor(IBlockAccess access, int x, int y, int z, Operation<Integer> original) {
|
||||
// Check ThreadLocal first (for worker threads), then fall back to world cache
|
||||
SmoothBiomeColorCache cache = SmoothBiomeColorCache.getActiveCache();
|
||||
if (cache != null) {
|
||||
return cache.getColor(SmoothBiomeColorCache.ColorType.GRASS, x, y, z);
|
||||
} else if (access instanceof WorldClientExtension ext) {
|
||||
return ext.celeritas$getSmoothBiomeColorCache().getColor(SmoothBiomeColorCache.ColorType.GRASS, x, y, z);
|
||||
} else {
|
||||
return original.call(access, x, y, z);
|
||||
}
|
||||
@Surround(method = "colorMultiplier")
|
||||
private void celeritas$smoothBlendColor(IBlockAccess access, int x, int y, int z) {
|
||||
@Surround.Carry
|
||||
final long color = BiomeVertexBlender.smoothColor(access, SmoothBiomeColorCache.ColorType.GRASS, x, y, z);
|
||||
@Surround.Skip
|
||||
final boolean skip = color >= 0;
|
||||
}
|
||||
|
||||
@Surround.Skipped
|
||||
private int celeritas$smoothBlendColorSkipped(@Surround.Carry long color) {
|
||||
return (int) color;
|
||||
}
|
||||
}
|
||||
+13
-14
@@ -1,25 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.celeritas.biome_blending;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeVertexBlender;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.WorldClientExtension;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import net.minecraft.block.BlockLeaves;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(BlockLeaves.class)
|
||||
public class MixinBlockLeaves {
|
||||
@WrapMethod(method = "colorMultiplier")
|
||||
private int celeritas$smoothBlendColor(IBlockAccess access, int x, int y, int z, Operation<Integer> original) {
|
||||
// Check ThreadLocal first (for worker threads), then fall back to world cache
|
||||
SmoothBiomeColorCache cache = SmoothBiomeColorCache.getActiveCache();
|
||||
if (cache != null) {
|
||||
return cache.getColor(SmoothBiomeColorCache.ColorType.FOLIAGE, x, y, z);
|
||||
} else if (access instanceof WorldClientExtension ext) {
|
||||
return ext.celeritas$getSmoothBiomeColorCache().getColor(SmoothBiomeColorCache.ColorType.FOLIAGE, x, y, z);
|
||||
} else {
|
||||
return original.call(access, x, y, z);
|
||||
}
|
||||
@Surround(method = "colorMultiplier")
|
||||
private void celeritas$smoothBlendColor(IBlockAccess access, int x, int y, int z) {
|
||||
@Surround.Carry
|
||||
final long color = BiomeVertexBlender.smoothColor(access, SmoothBiomeColorCache.ColorType.FOLIAGE, x, y, z);
|
||||
@Surround.Skip
|
||||
final boolean skip = color >= 0;
|
||||
}
|
||||
|
||||
@Surround.Skipped
|
||||
private int celeritas$smoothBlendColorSkipped(@Surround.Carry long color) {
|
||||
return (int) color;
|
||||
}
|
||||
}
|
||||
+13
-17
@@ -1,9 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.celeritas.biome_blending;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeVertexBlender;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.WorldClientExtension;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockLiquid;
|
||||
import net.minecraft.block.material.Material;
|
||||
@@ -16,19 +15,16 @@ public abstract class MixinBlockLiquid extends Block {
|
||||
super(material);
|
||||
}
|
||||
|
||||
@WrapMethod(method = "colorMultiplier")
|
||||
private int celeritas$smoothBlendColor(IBlockAccess access, int x, int y, int z, Operation<Integer> original) {
|
||||
if (this.blockMaterial != Material.water) {
|
||||
return original.call(access, x, y, z);
|
||||
}
|
||||
// Check ThreadLocal first (for worker threads), then fall back to world cache
|
||||
SmoothBiomeColorCache cache = SmoothBiomeColorCache.getActiveCache();
|
||||
if (cache != null) {
|
||||
return cache.getColor(SmoothBiomeColorCache.ColorType.WATER, x, y, z);
|
||||
} else if (access instanceof WorldClientExtension ext) {
|
||||
return ext.celeritas$getSmoothBiomeColorCache().getColor(SmoothBiomeColorCache.ColorType.WATER, x, y, z);
|
||||
} else {
|
||||
return original.call(access, x, y, z);
|
||||
}
|
||||
@Surround(method = "colorMultiplier")
|
||||
private void celeritas$smoothBlendColor(IBlockAccess access, int x, int y, int z) {
|
||||
@Surround.Carry
|
||||
final long color = this.blockMaterial == Material.water ? BiomeVertexBlender.smoothColor(access, SmoothBiomeColorCache.ColorType.WATER, x, y, z) : -1L;
|
||||
@Surround.Skip
|
||||
final boolean skip = color >= 0;
|
||||
}
|
||||
|
||||
@Surround.Skipped
|
||||
private int celeritas$smoothBlendColorSkipped(@Surround.Carry long color) {
|
||||
return (int) color;
|
||||
}
|
||||
}
|
||||
+86
-95
@@ -2,22 +2,17 @@ package com.gtnewhorizons.angelica.mixins.early.celeritas.biome_blending;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeBlendTessellator;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeVertexBlender;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.prupe.mcpatcher.cc.ColorizeBlock;
|
||||
import com.prupe.mcpatcher.ctm.CTMUtils;
|
||||
import jss.notfine.config.MCPatcherForgeConfig;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.BiomeBlendMode;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockGrass;
|
||||
import net.minecraft.client.renderer.EntityRenderer;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
@@ -32,27 +27,34 @@ public abstract class MixinRenderBlocks {
|
||||
@Shadow public abstract boolean hasOverrideBlockTexture();
|
||||
@Unique private final BiomeVertexBlender angelica$vertexBlender = new BiomeVertexBlender();
|
||||
|
||||
@WrapMethod(method = {"renderStandardBlock", "renderBlockLiquid"})
|
||||
private boolean angelica$blendBlock(Block block, int x, int y, int z, Operation<Boolean> original) {
|
||||
@Surround(method = {"renderStandardBlock", "renderBlockLiquid"}, id = "blendBlock")
|
||||
private void angelica$blendBlock(Block block, int x, int y, int z) {
|
||||
final SmoothBiomeColorCache cache = SmoothBiomeColorCache.getActiveCache();
|
||||
if (cache == null || ClientProxy.options().quality.biomeBlendMode != BiomeBlendMode.FANCY || SmoothBiomeColorCache.configuredRadius() == 0) {
|
||||
return original.call(block, x, y, z);
|
||||
}
|
||||
final BiomeBlendTessellator tessellator = (BiomeBlendTessellator) TessellatorManager.get();
|
||||
final BiomeVertexBlender previous = tessellator.angelica$getBiomeBlender();
|
||||
final SmoothBiomeColorCache.ColorType type = angelica$colorType(block);
|
||||
final boolean blend = previous == null && type != null && !hasOverrideBlockTexture() && !EntityRenderer.anaglyphEnable
|
||||
&& !(AngelicaConfig.enableMCPatcherForgeFeatures && MCPatcherForgeConfig.CustomColors.enabled && ColorizeBlock.hasCustomColors(block, blockAccess, x, y, z));
|
||||
if (blend) angelica$vertexBlender.setup(cache, type, x, y, z);
|
||||
tessellator.angelica$setBiomeBlender(blend ? angelica$vertexBlender : null);
|
||||
try {
|
||||
return original.call(block, x, y, z);
|
||||
} finally {
|
||||
tessellator.angelica$setBiomeBlender(previous);
|
||||
if (blend) angelica$vertexBlender.clear();
|
||||
@Surround.Carry("tessellator")
|
||||
BiomeBlendTessellator tessellator = null;
|
||||
@Surround.Carry("previous")
|
||||
BiomeVertexBlender previous = null;
|
||||
@Surround.Carry
|
||||
boolean blend = false;
|
||||
if (cache != null && ClientProxy.options().quality.biomeBlendMode == BiomeBlendMode.FANCY && SmoothBiomeColorCache.configuredRadius() != 0) {
|
||||
tessellator = (BiomeBlendTessellator) TessellatorManager.get();
|
||||
previous = tessellator.angelica$getBiomeBlender();
|
||||
final SmoothBiomeColorCache.ColorType type = angelica$colorType(block);
|
||||
blend = previous == null && type != null && !hasOverrideBlockTexture() && !EntityRenderer.anaglyphEnable
|
||||
&& !(AngelicaConfig.enableMCPatcherForgeFeatures && MCPatcherForgeConfig.CustomColors.enabled && ColorizeBlock.hasCustomColors(block, blockAccess, x, y, z));
|
||||
if (blend) angelica$vertexBlender.setup(cache, type, x, y, z);
|
||||
tessellator.angelica$setBiomeBlender(blend ? angelica$vertexBlender : null);
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("blendBlock")
|
||||
private void angelica$endBlendBlock(@Surround.Carry("tessellator") BiomeBlendTessellator tessellator, @Surround.Carry("previous") BiomeVertexBlender previous,
|
||||
@Surround.Carry boolean blend) {
|
||||
if (tessellator == null) return;
|
||||
tessellator.angelica$setBiomeBlender(previous);
|
||||
if (blend) angelica$vertexBlender.clear();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static SmoothBiomeColorCache.ColorType angelica$colorType(Block block) {
|
||||
if (block == Blocks.grass) return SmoothBiomeColorCache.ColorType.GRASS;
|
||||
@@ -61,98 +63,87 @@ public abstract class MixinRenderBlocks {
|
||||
return null;
|
||||
}
|
||||
|
||||
@WrapOperation(method = {"renderStandardBlock", "renderBlockLiquid"},
|
||||
@Surround(method = {"renderStandardBlock", "renderBlockLiquid"}, id = "deferTint",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;colorMultiplier(Lnet/minecraft/world/IBlockAccess;III)I"))
|
||||
private int angelica$deferLocalTint(Block block, IBlockAccess access, int x, int y, int z, Operation<Integer> original) {
|
||||
return ((BiomeBlendTessellator) TessellatorManager.get()).angelica$getBiomeBlender() == angelica$vertexBlender
|
||||
? angelica$vertexBlender.deferBlockTint() : original.call(block, access, x, y, z);
|
||||
private void angelica$deferTintEnter() {
|
||||
@Surround.Skip
|
||||
final boolean defer = ((BiomeBlendTessellator) TessellatorManager.get()).angelica$getBiomeBlender() == angelica$vertexBlender;
|
||||
}
|
||||
|
||||
@WrapOperation(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"},
|
||||
@Surround.Skipped("deferTint")
|
||||
private int angelica$deferTint() {
|
||||
return angelica$vertexBlender.deferBlockTint();
|
||||
}
|
||||
|
||||
@Surround(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"}, id = "faceYNeg",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderBlocks;renderFaceYNeg(Lnet/minecraft/block/Block;DDDLnet/minecraft/util/IIcon;)V"))
|
||||
private void angelica$blendYNeg(RenderBlocks renderer, Block block, double x, double y, double z, IIcon icon, Operation<Void> original) {
|
||||
angelica$blendFace(renderer, 0, block, x, y, z, icon, original);
|
||||
private void angelica$blendYNeg(RenderBlocks renderer, Block block, double x, double y, double z, IIcon icon) {
|
||||
@Surround.Carry
|
||||
final boolean tinted = angelica$vertexBlender.beginFace(renderer, 0, x, y, z, icon);
|
||||
}
|
||||
|
||||
@WrapOperation(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"},
|
||||
@Surround.Finally("faceYNeg")
|
||||
private void angelica$endYNeg(@Surround.Carry boolean tinted) {
|
||||
if (tinted) angelica$vertexBlender.endFace((RenderBlocks) (Object) this);
|
||||
}
|
||||
|
||||
@Surround(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"}, id = "faceYPos",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderBlocks;renderFaceYPos(Lnet/minecraft/block/Block;DDDLnet/minecraft/util/IIcon;)V"))
|
||||
private void angelica$blendYPos(RenderBlocks renderer, Block block, double x, double y, double z, IIcon icon, Operation<Void> original) {
|
||||
angelica$blendFace(renderer, 1, block, x, y, z, icon, original);
|
||||
private void angelica$blendYPos(RenderBlocks renderer, Block block, double x, double y, double z, IIcon icon) {
|
||||
@Surround.Carry
|
||||
final boolean tinted = angelica$vertexBlender.beginFace(renderer, 1, x, y, z, icon);
|
||||
}
|
||||
|
||||
@WrapOperation(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"},
|
||||
@Surround.Finally("faceYPos")
|
||||
private void angelica$endYPos(@Surround.Carry boolean tinted) {
|
||||
if (tinted) angelica$vertexBlender.endFace((RenderBlocks) (Object) this);
|
||||
}
|
||||
|
||||
@Surround(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"}, id = "faceZNeg",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderBlocks;renderFaceZNeg(Lnet/minecraft/block/Block;DDDLnet/minecraft/util/IIcon;)V"))
|
||||
private void angelica$blendZNeg(RenderBlocks renderer, Block block, double x, double y, double z, IIcon icon, Operation<Void> original) {
|
||||
angelica$blendFace(renderer, 2, block, x, y, z, icon, original);
|
||||
private void angelica$blendZNeg(RenderBlocks renderer, Block block, double x, double y, double z, IIcon icon) {
|
||||
@Surround.Carry
|
||||
final boolean tinted = angelica$vertexBlender.beginFace(renderer, 2, x, y, z, icon);
|
||||
}
|
||||
|
||||
@WrapOperation(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"},
|
||||
@Surround.Finally("faceZNeg")
|
||||
private void angelica$endZNeg(@Surround.Carry boolean tinted) {
|
||||
if (tinted) angelica$vertexBlender.endFace((RenderBlocks) (Object) this);
|
||||
}
|
||||
|
||||
@Surround(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"}, id = "faceZPos",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderBlocks;renderFaceZPos(Lnet/minecraft/block/Block;DDDLnet/minecraft/util/IIcon;)V"))
|
||||
private void angelica$blendZPos(RenderBlocks renderer, Block block, double x, double y, double z, IIcon icon, Operation<Void> original) {
|
||||
angelica$blendFace(renderer, 3, block, x, y, z, icon, original);
|
||||
private void angelica$blendZPos(RenderBlocks renderer, Block block, double x, double y, double z, IIcon icon) {
|
||||
@Surround.Carry
|
||||
final boolean tinted = angelica$vertexBlender.beginFace(renderer, 3, x, y, z, icon);
|
||||
}
|
||||
|
||||
@WrapOperation(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"},
|
||||
@Surround.Finally("faceZPos")
|
||||
private void angelica$endZPos(@Surround.Carry boolean tinted) {
|
||||
if (tinted) angelica$vertexBlender.endFace((RenderBlocks) (Object) this);
|
||||
}
|
||||
|
||||
@Surround(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"}, id = "faceXNeg",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderBlocks;renderFaceXNeg(Lnet/minecraft/block/Block;DDDLnet/minecraft/util/IIcon;)V"))
|
||||
private void angelica$blendXNeg(RenderBlocks renderer, Block block, double x, double y, double z, IIcon icon, Operation<Void> original) {
|
||||
angelica$blendFace(renderer, 4, block, x, y, z, icon, original);
|
||||
private void angelica$blendXNeg(RenderBlocks renderer, Block block, double x, double y, double z, IIcon icon) {
|
||||
@Surround.Carry
|
||||
final boolean tinted = angelica$vertexBlender.beginFace(renderer, 4, x, y, z, icon);
|
||||
}
|
||||
|
||||
@WrapOperation(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"},
|
||||
@Surround.Finally("faceXNeg")
|
||||
private void angelica$endXNeg(@Surround.Carry boolean tinted) {
|
||||
if (tinted) angelica$vertexBlender.endFace((RenderBlocks) (Object) this);
|
||||
}
|
||||
|
||||
@Surround(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"}, id = "faceXPos",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderBlocks;renderFaceXPos(Lnet/minecraft/block/Block;DDDLnet/minecraft/util/IIcon;)V"))
|
||||
private void angelica$blendXPos(RenderBlocks renderer, Block block, double x, double y, double z, IIcon icon, Operation<Void> original) {
|
||||
angelica$blendFace(renderer, 5, block, x, y, z, icon, original);
|
||||
private void angelica$blendXPos(RenderBlocks renderer, Block block, double x, double y, double z, IIcon icon) {
|
||||
@Surround.Carry
|
||||
final boolean tinted = angelica$vertexBlender.beginFace(renderer, 5, x, y, z, icon);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void angelica$blendFace(RenderBlocks renderer, int face, Block block, double x, double y, double z, IIcon icon, Operation<Void> original) {
|
||||
if (!angelica$vertexBlender.isActive()) {
|
||||
original.call(renderer, block, x, y, z, icon);
|
||||
return;
|
||||
}
|
||||
final Tessellator tessellator = TessellatorManager.get();
|
||||
if (((BiomeBlendTessellator) tessellator).angelica$getBiomeBlender() != angelica$vertexBlender) {
|
||||
original.call(renderer, block, x, y, z, icon);
|
||||
return;
|
||||
}
|
||||
final IIcon originalIcon = AngelicaConfig.enableMCPatcherForgeFeatures && MCPatcherForgeConfig.ConnectedTextures.enabled ? CTMUtils.getOriginalIcon(icon) : icon;
|
||||
if (angelica$vertexBlender.isGrass() && icon != Blocks.grass.getIcon(1, 0) && icon != BlockGrass.getIconSideOverlay()
|
||||
&& originalIcon != Blocks.grass.getIcon(1, 0) && originalIcon != BlockGrass.getIconSideOverlay()) {
|
||||
original.call(renderer, block, x, y, z, icon);
|
||||
return;
|
||||
}
|
||||
|
||||
final float rtl = renderer.colorRedTopLeft, gtl = renderer.colorGreenTopLeft, btl = renderer.colorBlueTopLeft;
|
||||
final float rbl = renderer.colorRedBottomLeft, gbl = renderer.colorGreenBottomLeft, bbl = renderer.colorBlueBottomLeft;
|
||||
final float rbr = renderer.colorRedBottomRight, gbr = renderer.colorGreenBottomRight, bbr = renderer.colorBlueBottomRight;
|
||||
final float rtr = renderer.colorRedTopRight, gtr = renderer.colorGreenTopRight, btr = renderer.colorBlueTopRight;
|
||||
final boolean ao = renderer.enableAO;
|
||||
final int ltl = renderer.brightnessTopLeft, lbl = renderer.brightnessBottomLeft;
|
||||
final int lbr = renderer.brightnessBottomRight, ltr = renderer.brightnessTopRight;
|
||||
final int color = tessellator.color, brightness = tessellator.brightness;
|
||||
if (!ao) {
|
||||
final int abgr = BiomeVertexBlender.nativeToABGR(color);
|
||||
renderer.colorRedTopLeft = renderer.colorRedBottomLeft = renderer.colorRedBottomRight = renderer.colorRedTopRight = (abgr & 255) / 255.0f;
|
||||
renderer.colorGreenTopLeft = renderer.colorGreenBottomLeft = renderer.colorGreenBottomRight = renderer.colorGreenTopRight = (abgr >> 8 & 255) / 255.0f;
|
||||
renderer.colorBlueTopLeft = renderer.colorBlueBottomLeft = renderer.colorBlueBottomRight = renderer.colorBlueTopRight = (abgr >> 16 & 255) / 255.0f;
|
||||
renderer.brightnessTopLeft = renderer.brightnessBottomLeft = renderer.brightnessBottomRight = renderer.brightnessTopRight = brightness;
|
||||
renderer.enableAO = true;
|
||||
}
|
||||
try {
|
||||
angelica$vertexBlender.tintFace(renderer, face, x, y, z);
|
||||
original.call(renderer, block, x, y, z, icon);
|
||||
} finally {
|
||||
renderer.colorRedTopLeft = rtl; renderer.colorGreenTopLeft = gtl; renderer.colorBlueTopLeft = btl;
|
||||
renderer.colorRedBottomLeft = rbl; renderer.colorGreenBottomLeft = gbl; renderer.colorBlueBottomLeft = bbl;
|
||||
renderer.colorRedBottomRight = rbr; renderer.colorGreenBottomRight = gbr; renderer.colorBlueBottomRight = bbr;
|
||||
renderer.colorRedTopRight = rtr; renderer.colorGreenTopRight = gtr; renderer.colorBlueTopRight = btr;
|
||||
renderer.enableAO = ao;
|
||||
if (!ao) {
|
||||
renderer.brightnessTopLeft = ltl; renderer.brightnessBottomLeft = lbl;
|
||||
renderer.brightnessBottomRight = lbr; renderer.brightnessTopRight = ltr;
|
||||
tessellator.color = color;
|
||||
tessellator.brightness = brightness;
|
||||
}
|
||||
}
|
||||
@Surround.Finally("faceXPos")
|
||||
private void angelica$endXPos(@Surround.Carry boolean tinted) {
|
||||
if (tinted) angelica$vertexBlender.endFace((RenderBlocks) (Object) this);
|
||||
}
|
||||
}
|
||||
+12
-6
@@ -1,10 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.celeritas.biome_blending;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SkyColorCache;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.world.World;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -15,17 +13,25 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
public abstract class MixinWorld {
|
||||
@Unique private final SkyColorCache angelica$skyColorCache = new SkyColorCache();
|
||||
|
||||
@WrapOperation(
|
||||
@Surround(
|
||||
method = "getSkyColorBody",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraftforge/client/ForgeHooksClient;getSkyBlendColour(Lnet/minecraft/world/World;III)I",
|
||||
remap = false),
|
||||
remap = false)
|
||||
private int angelica$blendSkyColor(World world, int x, int y, int z, Operation<Integer> original, @Local(argsOnly = true) Entity camera, @Local(argsOnly = true) float tickDelta) {
|
||||
return angelica$skyColorCache.getColor(world,
|
||||
private void angelica$blendSkyColor(World world, @Surround.Local(argsOnly = true) Entity camera, @Surround.Local(argsOnly = true) float tickDelta) {
|
||||
@Surround.Carry
|
||||
final int color = angelica$skyColorCache.getColor(world,
|
||||
camera.lastTickPosX + (camera.posX - camera.lastTickPosX) * tickDelta,
|
||||
camera.lastTickPosY + (camera.posY - camera.lastTickPosY) * tickDelta,
|
||||
camera.lastTickPosZ + (camera.posZ - camera.lastTickPosZ) * tickDelta,
|
||||
SmoothBiomeColorCache.configuredRadius());
|
||||
@Surround.Skip
|
||||
final boolean skip = true;
|
||||
}
|
||||
|
||||
@Surround.Skipped
|
||||
private int angelica$blendedSkyColor(@Surround.Carry int color) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
+9
-11
@@ -1,20 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.celeritas.features.culling;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.rendering.particles.ParticleCulling;
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import net.minecraft.client.particle.EffectRenderer;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(EffectRenderer.class)
|
||||
public class MixinEffectRenderer {
|
||||
@@ -28,8 +25,8 @@ public class MixinEffectRenderer {
|
||||
@Unique
|
||||
private boolean particleVisible;
|
||||
|
||||
@Inject(method = {"renderParticles", "renderLitParticles"}, at = @At("HEAD"))
|
||||
private void setupViewport(Entity player, float partialTickTime, CallbackInfo ci) {
|
||||
@Surround(id = "viewport", method = {"renderParticles", "renderLitParticles"})
|
||||
private void setupViewport(Entity player, float partialTickTime) {
|
||||
final boolean useCulling = ClientProxy.options().advanced.useParticleCulling;
|
||||
if(useCulling) {
|
||||
this.cullingRenderer = CeleritasWorldRenderer.getInstanceOrNull();
|
||||
@@ -39,8 +36,8 @@ public class MixinEffectRenderer {
|
||||
if (Tracy.ENABLED) Tracy.beginZone(angelica$Z_PARTICLE_PASS);
|
||||
}
|
||||
|
||||
@Inject(method = {"renderParticles", "renderLitParticles"}, at = @At("RETURN"))
|
||||
private void endParticlePass(Entity player, float partialTickTime, CallbackInfo ci) {
|
||||
@Surround.Finally("viewport")
|
||||
private void endParticlePass() {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
|
||||
@@ -50,8 +47,9 @@ public class MixinEffectRenderer {
|
||||
return this.particleVisible ? particle.getBrightnessForRender(partialTicks) : 0;
|
||||
}
|
||||
|
||||
@WrapWithCondition(method = {"renderParticles", "renderLitParticles"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/EntityFX;renderParticle(Lnet/minecraft/client/renderer/Tessellator;FFFFFF)V"))
|
||||
private boolean renderParticles(EntityFX particle, Tessellator tessellator, float partialTicks, float rotationX, float rotationXZ, float rotationZ, float rotationYZ, float rotationXY) {
|
||||
return this.particleVisible;
|
||||
@Surround(id = "cull", method = {"renderParticles", "renderLitParticles"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/EntityFX;renderParticle(Lnet/minecraft/client/renderer/Tessellator;FFFFFF)V"))
|
||||
private void cullInvisible() {
|
||||
@Surround.Skip
|
||||
boolean culled = !this.particleVisible;
|
||||
}
|
||||
}
|
||||
+11
-13
@@ -5,16 +5,15 @@ import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.TextureMapExtension;
|
||||
import com.gtnewhorizons.angelica.textures.atlas.AtlasAssembler;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.utils.MipmapStrategies;
|
||||
import com.gtnewhorizons.angelica.utils.MipmapStrategy;
|
||||
import com.gtnewhorizons.angelica.utils.SpritePadding;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.IIconRegister;
|
||||
import net.minecraft.client.renderer.texture.Stitcher;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
@@ -96,18 +95,18 @@ public class MixinTextureMap implements TextureMapExtension {
|
||||
/**
|
||||
* Brackets each block's icon registration so the sprites it registers can be attributed to it.
|
||||
*/
|
||||
@WrapOperation(
|
||||
@Surround(
|
||||
method = "registerIcons",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/block/Block;registerBlockIcons(Lnet/minecraft/client/renderer/texture/IIconRegister;)V"))
|
||||
private void angelica$trackBlockIcons(Block block, IIconRegister reg, Operation<Void> original) {
|
||||
private void angelica$trackBlockIcons(Block block) {
|
||||
MipmapStrategies.beginBlock(block);
|
||||
try {
|
||||
original.call(block, reg);
|
||||
} finally {
|
||||
MipmapStrategies.endBlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally
|
||||
private void angelica$trackBlockIconsEnd() {
|
||||
MipmapStrategies.endBlock();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,11 +146,10 @@ public class MixinTextureMap implements TextureMapExtension {
|
||||
SpritePadding.uploadPadded(frameData, width, height, originX, originY, ext.angelica$getGutterWidth(), blur, clamp);
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
@ModifyExpressionValue(
|
||||
method = "loadTextureAtlas",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/texture/Stitcher;getStichSlots()Ljava/util/List;"))
|
||||
private List<TextureAtlasSprite> angelica$assembleAtlas(Stitcher stitcher, Operation<List<TextureAtlasSprite>> original) {
|
||||
final List<TextureAtlasSprite> sprites = original.call(stitcher);
|
||||
private List<TextureAtlasSprite> angelica$uploadAtlas(List<TextureAtlasSprite> sprites, @Local Stitcher stitcher) {
|
||||
AtlasAssembler.upload(sprites, this.mipmapLevels, stitcher.getCurrentWidth(), stitcher.getCurrentHeight());
|
||||
return sprites;
|
||||
}
|
||||
|
||||
Loaded 100 of 139 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user