11package org .embeddedt .modernfix .core ;
22
3+ import com .google .common .collect .ImmutableSet ;
34import org .apache .logging .log4j .LogManager ;
45import org .apache .logging .log4j .Logger ;
56import org .embeddedt .modernfix .core .config .ModernFixEarlyConfig ;
67import org .embeddedt .modernfix .core .config .Option ;
78import org .embeddedt .modernfix .platform .ModernFixPlatformHooks ;
89import org .embeddedt .modernfix .world .ThreadDumper ;
10+ import org .objectweb .asm .Opcodes ;
11+ import org .objectweb .asm .Type ;
912import org .objectweb .asm .tree .*;
1013import org .spongepowered .asm .mixin .extensibility .IMixinConfigPlugin ;
1114import org .spongepowered .asm .mixin .extensibility .IMixinInfo ;
15+ import org .spongepowered .asm .mixin .transformer .meta .MixinMerged ;
1216
1317import java .io .File ;
1418import java .util .*;
@@ -146,6 +150,111 @@ public void preApply(String targetClassName, ClassNode targetClass, String mixin
146150
147151 @ Override
148152 public void postApply (String targetClassName , ClassNode targetClass , String mixinClassName , IMixinInfo mixinInfo ) {
153+ if (mixinClassName .equals ("org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds.BlockStateBaseMixin" )) {
154+ try {
155+ applyBlockStateCacheScan (targetClass );
156+ } catch (RuntimeException e ) {
157+ ModernFixMixinPlugin .instance .logger .error ("Applying blockstate cache ASM patch failed" , e );
158+ }
159+ }
149160 ModernFixPlatformHooks .INSTANCE .applyASMTransformers (mixinClassName , targetClass );
150161 }
162+
163+ private void applyBlockStateCacheScan (ClassNode targetClass ) {
164+ Set <String > initCacheMethodNames = ImmutableSet .of ("m_60611_" , "func_215692_c" , "method_26200" , "initCache" );
165+ Set <String > whitelistedInjections = ImmutableSet .of (
166+ "getFluidState" , "method_26227" , "m_60819_" , "func_204520_s"
167+ );
168+ Map <String , MethodNode > injectorMethodNames = new HashMap <>();
169+ Map <String , String > injectorMixinSource = new HashMap <>();
170+ String descriptor = Type .getDescriptor (MixinMerged .class );
171+ for (MethodNode m : targetClass .methods ) {
172+ if ((m .access & Opcodes .ACC_STATIC ) != 0 )
173+ continue ;
174+ Set <AnnotationNode > seenNodes = new HashSet <>();
175+ if (m .invisibleAnnotations != null ) {
176+ for (AnnotationNode ann : m .invisibleAnnotations ) {
177+ if (ann .desc .equals (descriptor )) {
178+ seenNodes .add (ann );
179+ }
180+ }
181+ }
182+ if (m .visibleAnnotations != null ) {
183+ for (AnnotationNode ann : m .visibleAnnotations ) {
184+ if (ann .desc .equals (descriptor )) {
185+ seenNodes .add (ann );
186+ }
187+ }
188+ }
189+ if (seenNodes .size () > 0 ) {
190+ injectorMethodNames .put (m .name , m );
191+ for (AnnotationNode node : seenNodes ) {
192+ for (int i = 0 ; i < node .values .size (); i += 2 ) {
193+ if (Objects .equals (node .values .get (i ), "mixin" )) {
194+ injectorMixinSource .put (m .name , (String )node .values .get (i + 1 ));
195+ break ;
196+ }
197+ }
198+ }
199+ }
200+ }
201+ Set <String > cacheCalledInjectors = new HashSet <>();
202+ // Search for initCache in the class
203+ for (MethodNode m : targetClass .methods ) {
204+ if ((m .access & Opcodes .ACC_STATIC ) != 0 )
205+ continue ;
206+ if (initCacheMethodNames .contains (m .name )) {
207+ // This is it. Check for any injectors it calls
208+ for (AbstractInsnNode n : m .instructions ) {
209+ if (n instanceof MethodInsnNode ) {
210+ MethodInsnNode invoke = (MethodInsnNode )n ;
211+ if (((MethodInsnNode )n ).owner .equals (targetClass .name ) && injectorMethodNames .containsKey (((MethodInsnNode )n ).name )) {
212+ cacheCalledInjectors .add (invoke .name );
213+ }
214+ }
215+ }
216+ break ;
217+ }
218+ }
219+ Set <String > accessedFieldNames = new HashSet <>();
220+ // We now know all methods that have been injected into initCache. See what fields they write to
221+ injectorMethodNames .forEach ((name , method ) -> {
222+ if (cacheCalledInjectors .contains (name )) {
223+ for (AbstractInsnNode n : method .instructions ) {
224+ if (n instanceof FieldInsnNode ) {
225+ FieldInsnNode fieldAcc = (FieldInsnNode )n ;
226+ if (fieldAcc .getOpcode () == Opcodes .PUTFIELD && fieldAcc .owner .equals (targetClass .name )) {
227+ accessedFieldNames .add (fieldAcc .name );
228+ }
229+ }
230+ }
231+ }
232+ });
233+ // Lastly, scan all injected methods and see if they retrieve from the field. If so, inject a generateCache
234+ // call at the start.
235+ injectorMethodNames .forEach ((name , method ) -> {
236+ // skip whitelisted injectors, and injectors called by initCache itself (to prevent recursion)
237+ if (whitelistedInjections .contains (name ) || cacheCalledInjectors .contains (name ))
238+ return ;
239+ boolean needInjection = false ;
240+ for (AbstractInsnNode n : method .instructions ) {
241+ if (n instanceof FieldInsnNode ) {
242+ FieldInsnNode fieldAcc = (FieldInsnNode )n ;
243+ if (fieldAcc .getOpcode () == Opcodes .GETFIELD && accessedFieldNames .contains (fieldAcc .name )) {
244+ needInjection = true ;
245+ break ;
246+ }
247+ }
248+ }
249+ if (needInjection ) {
250+ ModernFixMixinPlugin .instance .logger .info ("Injecting BlockStateBase cache population hook into {} from {}" ,
251+ name , injectorMixinSource .getOrDefault (name , "[unknown mixin]" ));
252+ // inject this.mfix$generateCache() at method head
253+ InsnList injection = new InsnList ();
254+ injection .add (new VarInsnNode (Opcodes .ALOAD , 0 ));
255+ injection .add (new MethodInsnNode (Opcodes .INVOKEVIRTUAL , targetClass .name , "mfix$generateCache" , "()V" ));
256+ method .instructions .insert (injection );
257+ }
258+ });
259+ }
151260}
0 commit comments