Skip to content

Commit fc07123

Browse files
committed
interop with Angelica's ThreadSafeISBRH api
1 parent ae4a57d commit fc07123

16 files changed

Lines changed: 638 additions & 85 deletions

src/main/java/com/falsepattern/falsetweaks/FalseTweaks.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
package com.falsepattern.falsetweaks;
2525

26+
import com.falsepattern.falsetweaks.config.ModuleConfig;
2627
import com.falsepattern.falsetweaks.proxy.CommonProxy;
2728

2829
import cpw.mods.fml.common.Loader;
@@ -66,6 +67,9 @@ private static void builtinMod(String modname) {
6667
@Mod.EventHandler
6768
public void preInit(FMLPreInitializationEvent e) {
6869
proxy.preInit(e);
70+
if (Loader.isModLoaded("angelica") && ModuleConfig.THREADED_CHUNK_UPDATES()) {
71+
createSidedException("FalseTweaks threaded rendering is not compatible with Angelica.\nPlease disable it in the FalseTweaks config.");
72+
}
6973
if (Loader.isModLoaded("animfix")) {
7074
builtinMod("animfix");
7175
}

src/main/java/com/falsepattern/falsetweaks/asm/CoreLoadingPlugin.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
import net.minecraft.launchwrapper.IClassTransformer;
4141

42+
import java.util.ArrayList;
4243
import java.util.List;
4344
import java.util.Map;
4445
import java.util.Set;
@@ -85,7 +86,12 @@ public String[] getASMTransformerClass() {
8586
FIELD_HACK_TF = new FalseTweaksFieldHackTransformer();
8687
mixinTweakClasses.add(MixinCompatHackTweaker.class.getName());
8788
}
88-
return new String[]{Tags.ROOT_PKG + ".asm.FalseTweaksTransformer"};
89+
val xFormers = new ArrayList<String>();
90+
if (FMLLaunchHandler.side().isClient() && ModuleConfig.THREADED_CHUNK_UPDATES()) {
91+
xFormers.add(Tags.ROOT_PKG + ".asm.modules.threadedupdates.compat.Threading_AngelicaRemapper");
92+
}
93+
xFormers.add(Tags.ROOT_PKG + ".asm.FalseTweaksTransformer");
94+
return xFormers.toArray(new String[0]);
8995
}
9096

9197
@Override

src/main/java/com/falsepattern/falsetweaks/asm/FalseTweaksTransformer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.falsepattern.falsetweaks.asm.modules.threadedupdates.*;
2929
import com.falsepattern.falsetweaks.asm.modules.threadedupdates.block.Threading_BlockMinMax;
3030
import com.falsepattern.falsetweaks.asm.modules.threadedupdates.block.Threading_BlockMinMaxRedirector;
31+
import com.falsepattern.falsetweaks.asm.modules.threadedupdates.compat.Threading_AngelicaCompatFixer;
3132
import com.falsepattern.falsetweaks.asm.modules.threadedupdates.settings.Threading_GameSettings;
3233
import com.falsepattern.falsetweaks.asm.modules.threadedupdates.settings.Threading_GameSettingsRedirector;
3334
import com.falsepattern.falsetweaks.config.ModuleConfig;
@@ -53,6 +54,7 @@ private static List<TurboClassTransformer> transformers() {
5354
if (FMLLaunchHandler.side().isClient()) {
5455
transformers.add(new RenderGlobalDeOptimizer());
5556
if (ModuleConfig.THREADED_CHUNK_UPDATES()) {
57+
transformers.add(new Threading_AngelicaCompatFixer());
5658
transformers.add(new Threading_RenderBlocksASM());
5759
transformers.add(new Threading_TessellatorUseReplacement());
5860
transformers.add(new Threading_ThreadSafeBlockRendererInjector());

src/main/java/com/falsepattern/falsetweaks/asm/modules/threadedupdates/Threading_ThreadSafeBlockRendererInjector.java

Lines changed: 113 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,15 @@
3131
import org.objectweb.asm.Handle;
3232
import org.objectweb.asm.Opcodes;
3333
import org.objectweb.asm.Type;
34+
import org.objectweb.asm.tree.ClassNode;
3435
import org.objectweb.asm.tree.FieldInsnNode;
3536
import org.objectweb.asm.tree.FieldNode;
37+
import org.objectweb.asm.tree.FrameNode;
3638
import org.objectweb.asm.tree.InnerClassNode;
3739
import org.objectweb.asm.tree.InsnNode;
3840
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
41+
import org.objectweb.asm.tree.JumpInsnNode;
42+
import org.objectweb.asm.tree.LabelNode;
3943
import org.objectweb.asm.tree.MethodInsnNode;
4044
import org.objectweb.asm.tree.MethodNode;
4145
import org.objectweb.asm.tree.TypeInsnNode;
@@ -47,16 +51,25 @@
4751
import java.util.Set;
4852

4953
// TODO ASM Logging
54+
5055
public class Threading_ThreadSafeBlockRendererInjector implements TurboClassTransformer {
5156
private static final Set<String> CLASS_NAMES = new HashSet<>();
5257
private static final Set<String> INTERNAL_NAMES = new HashSet<>();
5358
private static final Map<String, Handle> INITIALIZERS = new HashMap<>();
5459
private static final Map<String, String> SUPPLIERS = new HashMap<>();
60+
private static final Set<String> FACTORIES = new HashSet<>();
5561
private static final String TSBR_InternalName = "com/falsepattern/falsetweaks/api/threading/ThreadSafeBlockRenderer";
5662
private static final String ISBR_InternalName = "cpw/mods/fml/client/registry/ISimpleBlockRenderingHandler";
5763
private static final Handle LAMBDA_META_FACTORY = new Handle(Opcodes.H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory",
5864
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;");
5965

66+
public static final String THREAD_SAFE_ANNOTATION_InternalName = "com/falsepattern/falsetweaks/modules/threadedupdates/interop/ThreadSafeISBRH";
67+
public static final String THREAD_SAFE_ANNOTATION_DESC = "Lcom/falsepattern/falsetweaks/modules/threadedupdates/interop/ThreadSafeISBRH;";
68+
public static final String THREAD_SAFE_FACTORY_InternalName = "com/falsepattern/falsetweaks/modules/threadedupdates/interop/ThreadSafeISBRHFactory";
69+
70+
public static final String FACTORY_METHOD_DESC = "()L" + THREAD_SAFE_FACTORY_InternalName + ";";
71+
public static final String FACTORY_METHOD_NAME = "newInstance";
72+
6073
private static final String[] HARDCODED = new String[] {
6174
"com.carpentersblocks.renderer.BlockHandlerCarpentersBarrier:default!",
6275
"com.carpentersblocks.renderer.BlockHandlerCarpentersBed:default!",
@@ -101,7 +114,7 @@ public static void addAll(String... entries) {
101114
creatorHandle = new Handle(Opcodes.H_NEWINVOKESPECIAL, internalName, "<init>", "()V");
102115
} else {
103116
val genParts = generator.split("!");
104-
val genInternalName = internalName.replace('.', '/');
117+
val genInternalName = genParts[0].replace('.', '/');
105118
val genMethodName = genParts[1];
106119
creatorHandle = new Handle(Opcodes.H_INVOKESTATIC, genInternalName, genMethodName, "()L" + internalName + ";");
107120
}
@@ -128,7 +141,47 @@ public String name() {
128141

129142
@Override
130143
public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
131-
return CLASS_NAMES.contains(className);
144+
if (CLASS_NAMES.contains(className)) {
145+
return true;
146+
} else {
147+
val node = classNode.getNode();
148+
if (node == null)
149+
return false;
150+
val anns = node.visibleAnnotations;
151+
if (anns == null)
152+
return false;
153+
val internalName = className.replace('.', '/');
154+
for (val ann: anns) {
155+
if (THREAD_SAFE_ANNOTATION_DESC.equals(ann.desc)) {
156+
boolean perThread = false;
157+
val values = ann.values;
158+
if (values != null) {
159+
val iter = values.iterator();
160+
while (iter.hasNext()) {
161+
val name = iter.next();
162+
val value = iter.next();
163+
if ("perThread".equals(name)) {
164+
perThread = (Boolean) value;
165+
}
166+
}
167+
}
168+
if (perThread) {
169+
INITIALIZERS.put(internalName, new Handle(Opcodes.H_NEWINVOKESPECIAL, internalName, "<init>", "()V"));
170+
}
171+
return true;
172+
}
173+
}
174+
val ifcs = node.interfaces;
175+
if (ifcs == null)
176+
return false;
177+
for (val ifc: ifcs) {
178+
if (THREAD_SAFE_FACTORY_InternalName.equals(ifc)) {
179+
FACTORIES.add(className.replace('.', '/'));
180+
return true;
181+
}
182+
}
183+
}
184+
return false;
132185
}
133186

134187
@Override
@@ -141,52 +194,80 @@ public boolean transformClass(@NotNull String className, @NotNull ClassNodeHandl
141194

142195
val internalName = className.replace('.', '/');
143196
cn.interfaces.add(TSBR_InternalName);
144-
if (INITIALIZERS.containsKey(internalName)) {
145-
cn.innerClasses.add(new InnerClassNode("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL));
146-
cn.fields.add(new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "ft$tlInjected", "Ljava/lang/ThreadLocal;", null, null));
147-
boolean staticInitializedFound = false;
148-
for (val method : cn.methods) {
149-
if (!"<clinit>".equals(method.name))
150-
continue;
151-
staticInitializedFound = true;
152-
injectInstanceCreation(method, internalName);
153-
}
154-
if (!staticInitializedFound) {
155-
val clinit = new MethodNode(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
156-
cn.methods.add(clinit);
157-
injectInstanceCreation(clinit, internalName);
158-
clinit.instructions.add(new InsnNode(Opcodes.RETURN));
159-
}
160-
}
161197
val getter = new MethodNode(Opcodes.ACC_PUBLIC, "forCurrentThread", "()L" + ISBR_InternalName + ";", null, null);
162198
cn.methods.add(getter);
163199
val insnList = getter.instructions;
164-
if (SUPPLIERS.containsKey(internalName)) {
165-
val supplier = SUPPLIERS.get(internalName);
166-
val parts = supplier.split("\\?");
167-
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, parts[0], parts[1], "()L" + internalName + ";", false));
168-
} else if (INITIALIZERS.containsKey(internalName)) {
200+
if (INITIALIZERS.containsKey(internalName)) {
201+
injectThreadLocal(cn, internalName, true);
169202
insnList.add(new FieldInsnNode(Opcodes.GETSTATIC, internalName, "ft$tlInjected", "Ljava/lang/ThreadLocal;"));
170203
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/ThreadLocal", "get", "()Ljava/lang/Object;", false));
171204
insnList.add(new TypeInsnNode(Opcodes.CHECKCAST, ISBR_InternalName));
205+
getter.maxStack = 1;
206+
} else if (FACTORIES.contains(internalName)) {
207+
injectThreadLocal(cn, internalName, false);
208+
insnList.add(new FieldInsnNode(Opcodes.GETSTATIC, internalName, "ft$tlInjected", "Ljava/lang/ThreadLocal;"));
209+
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/ThreadLocal", "get", "()Ljava/lang/Object;", false));
210+
insnList.add(new InsnNode(Opcodes.DUP));
211+
val nonNull = new LabelNode();
212+
insnList.add(new JumpInsnNode(Opcodes.IFNONNULL, nonNull));
213+
insnList.add(new InsnNode(Opcodes.POP));
214+
insnList.add(new FieldInsnNode(Opcodes.GETSTATIC, internalName, "ft$tlInjected", "Ljava/lang/ThreadLocal;"));
215+
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
216+
insnList.add(new TypeInsnNode(Opcodes.CHECKCAST, THREAD_SAFE_FACTORY_InternalName));
217+
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, THREAD_SAFE_FACTORY_InternalName, FACTORY_METHOD_NAME, FACTORY_METHOD_DESC, true));
218+
insnList.add(new InsnNode(Opcodes.DUP_X1));
219+
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/ThreadLocal", "set", "(Ljava/lang/Object;)V", false));
220+
insnList.add(nonNull);
221+
insnList.add(new FrameNode(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Object"}));
222+
insnList.add(new TypeInsnNode(Opcodes.CHECKCAST, ISBR_InternalName));
223+
getter.maxStack = 3;
224+
} else if (SUPPLIERS.containsKey(internalName)) {
225+
val supplier = SUPPLIERS.get(internalName);
226+
val parts = supplier.split("\\?");
227+
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, parts[0], parts[1], "()L" + internalName + ";", false));
228+
getter.maxStack = 1;
172229
} else {
173230
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
231+
getter.maxStack = 1;
174232
}
175233
insnList.add(new InsnNode(Opcodes.ARETURN));
176-
getter.maxStack = 1;
177234
getter.maxLocals = 1;
178235
return true;
179236
}
180237

238+
private void injectThreadLocal(ClassNode cn, String internalName, boolean withInitial) {
239+
if (withInitial) {
240+
cn.innerClasses.add(new InnerClassNode("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL));
241+
}
242+
cn.fields.add(new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "ft$tlInjected", "Ljava/lang/ThreadLocal;", null, null));
243+
boolean staticInitializedFound = false;
244+
for (val method : cn.methods) {
245+
if (!"<clinit>".equals(method.name))
246+
continue;
247+
staticInitializedFound = true;
248+
injectThreadLocalCreation(method, internalName, withInitial);
249+
}
250+
if (!staticInitializedFound) {
251+
val clinit = new MethodNode(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
252+
clinit.instructions.add(new FrameNode(Opcodes.F_FULL, 0, new Object[0], 0, new Object[0]));
253+
cn.methods.add(clinit);
254+
injectThreadLocalCreation(clinit, internalName, withInitial);
255+
clinit.instructions.add(new InsnNode(Opcodes.RETURN));
256+
}
257+
}
181258

182-
private void injectInstanceCreation(MethodNode method, String internalName) {
259+
private void injectThreadLocalCreation(MethodNode method, String internalName, boolean withInitial) {
183260
val insnList = method.instructions.iterator();
184-
insnList.add(new InvokeDynamicInsnNode("get", "()Ljava/util/function/Supplier;",
185-
LAMBDA_META_FACTORY,
186-
Type.getType("()Ljava/lang/Object;"),
187-
INITIALIZERS.get(internalName),
188-
Type.getType("()L" + internalName + ";")));
189-
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/ThreadLocal", "withInitial", "(Ljava/util/function/Supplier;)Ljava/lang/ThreadLocal;", false));
261+
if (withInitial) {
262+
insnList.add(
263+
new InvokeDynamicInsnNode("get", "()Ljava/util/function/Supplier;", LAMBDA_META_FACTORY, Type.getType("()Ljava/lang/Object;"), INITIALIZERS.get(internalName),
264+
Type.getType("()L" + internalName + ";")));
265+
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/ThreadLocal", "withInitial", "(Ljava/util/function/Supplier;)Ljava/lang/ThreadLocal;", false));
266+
} else {
267+
insnList.add(new TypeInsnNode(Opcodes.NEW, "java/lang/ThreadLocal"));
268+
insnList.add(new InsnNode(Opcodes.DUP));
269+
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/ThreadLocal", "<init>", "()V", false));
270+
}
190271
insnList.add(new FieldInsnNode(Opcodes.PUTSTATIC, internalName, "ft$tlInjected", "Ljava/lang/ThreadLocal;"));
191272
if (method.maxStack == 0) {
192273
method.maxStack = 1;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* This file is part of FalseTweaks.
3+
*
4+
* Copyright (C) 2022-2024 FalsePattern
5+
* All Rights Reserved
6+
*
7+
* The above copyright notice and this permission notice shall be included
8+
* in all copies or substantial portions of the Software.
9+
*
10+
* FalseTweaks is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU Lesser General Public License as published by
12+
* the Free Software Foundation, either version 3 of the License, or
13+
* (at your option) any later version.
14+
*
15+
* FalseTweaks is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU Lesser General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU Lesser General Public License
21+
* along with FalseTweaks. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
24+
package com.falsepattern.falsetweaks.asm.modules.threadedupdates.compat;
25+
26+
import com.falsepattern.falsetweaks.asm.modules.threadedupdates.Threading_ThreadSafeBlockRendererInjector;
27+
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
28+
import org.objectweb.asm.ClassVisitor;
29+
import org.objectweb.asm.commons.Remapper;
30+
import org.objectweb.asm.commons.RemappingClassAdapter;
31+
32+
class AngelicaRemappingAdapter extends RemappingClassAdapter {
33+
AngelicaRemappingAdapter(ClassVisitor cv) {
34+
super(cv, AngelicaRemapper.INSTANCE);
35+
}
36+
37+
private static class AngelicaRemapper extends Remapper {
38+
public static final AngelicaRemapper INSTANCE = new AngelicaRemapper();
39+
private static final Object2ObjectOpenHashMap<String, String> MAPPINGS = new Object2ObjectOpenHashMap<>();
40+
static {
41+
MAPPINGS.put("com/gtnewhorizons/angelica/api/ThreadSafeISBRH", Threading_ThreadSafeBlockRendererInjector.THREAD_SAFE_ANNOTATION_InternalName);
42+
MAPPINGS.put("com/gtnewhorizons/angelica/api/ThreadSafeISBRHFactory", Threading_ThreadSafeBlockRendererInjector.THREAD_SAFE_FACTORY_InternalName);
43+
}
44+
45+
@Override
46+
public String map(String typeName) {
47+
return MAPPINGS.getOrDefault(typeName, typeName);
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)