diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/faster_capabilities/CapabilityProviderMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/faster_capabilities/CapabilityProviderMixin.java deleted file mode 100644 index 6e68f94a..00000000 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/faster_capabilities/CapabilityProviderMixin.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.embeddedt.modernfix.common.mixin.perf.faster_capabilities; - -import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import net.minecraft.nbt.CompoundTag; -import net.minecraftforge.common.capabilities.CapabilityProvider; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -import java.util.Objects; - -@Mixin(value = CapabilityProvider.class, remap = false) -@SuppressWarnings("rawtypes") -public class CapabilityProviderMixin { - @Shadow - private boolean isLazy; - - @Shadow - private boolean initialized; - - @Shadow - private CompoundTag lazyData; - - /** - * @author embeddedt - * @reason avoid initializing capabilities in the case where we can reasonably expect the lazy data - * to deserialize to the same concrete caps - */ - @WrapMethod(method = "areCapsCompatible(Lnet/minecraftforge/common/capabilities/CapabilityProvider;)Z") - private boolean mfix$skipLazyInit(CapabilityProvider other, Operation original) { - if (this.isLazy && !this.initialized) { - var otherAcc = ((CapabilityProviderMixin)(Object)other); - if (otherAcc.isLazy && !otherAcc.initialized && otherAcc.getClass() == this.getClass() && Objects.equals(this.lazyData, otherAcc.lazyData)) { - return true; - } - } - return original.call(other); - } -} diff --git a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java index c6a52cf1..05bf9147 100644 --- a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java +++ b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java @@ -5,6 +5,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig; import org.embeddedt.modernfix.core.config.Option; +import org.embeddedt.modernfix.core.launchplugin.CoreLaunchPluginService; import org.embeddedt.modernfix.platform.ModernFixPlatformHooks; import org.embeddedt.modernfix.world.ThreadDumper; import org.objectweb.asm.Opcodes; @@ -109,7 +110,7 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { @Override public void onLoad(String mixinPackage) { - + CoreLaunchPluginService.install(); } @Override diff --git a/src/main/java/org/embeddedt/modernfix/core/launchplugin/CoreLaunchPluginService.java b/src/main/java/org/embeddedt/modernfix/core/launchplugin/CoreLaunchPluginService.java new file mode 100644 index 00000000..01db62fe --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/core/launchplugin/CoreLaunchPluginService.java @@ -0,0 +1,73 @@ +package org.embeddedt.modernfix.core.launchplugin; + +import cpw.mods.modlauncher.LaunchPluginHandler; +import cpw.mods.modlauncher.Launcher; +import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; +import org.embeddedt.modernfix.core.launchplugin.transformer.CapabilityProviderTransformer; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.Map; + +public class CoreLaunchPluginService implements ILaunchPluginService { + private static final Logger LOGGER = LoggerFactory.getLogger("ModernFixLaunchPlugin"); + + public static void install() { + try { + Field launchPluginsField = Launcher.class.getDeclaredField("launchPlugins"); + launchPluginsField.setAccessible(true); + LaunchPluginHandler launchPluginHandler = (LaunchPluginHandler) launchPluginsField.get(Launcher.INSTANCE); + Field pluginsField = LaunchPluginHandler.class.getDeclaredField("plugins"); + pluginsField.setAccessible(true); + Map plugins = (Map)pluginsField.get(launchPluginHandler); + var service = new CoreLaunchPluginService(); + try { + plugins.put(service.name(), service); + } catch (Exception e) { + var newMap = new LinkedHashMap<>(plugins); + newMap.put(service.name(), service); + pluginsField.set(launchPluginHandler, newMap); + } + } catch(Exception e) { + LOGGER.error("Error installing launch plugin service", e); + } + } + + @Override + public String name() { + return "modernfix"; + } + + private static final EnumSet GO = EnumSet.of(Phase.AFTER); + private static final EnumSet NOGO = EnumSet.noneOf(Phase.class); + + private static final Map TRANSFORMERS = Map.of( + "net.minecraftforge.common.capabilities.CapabilityProvider", new CapabilityProviderTransformer() + ); + + @Override + public EnumSet handlesClass(Type classType, boolean isEmpty) { + return !isEmpty && TRANSFORMERS.containsKey(classType.getClassName()) ? GO : NOGO; + } + + @Override + public int processClassWithFlags(Phase phase, ClassNode classNode, Type classType, String reason) { + if (classNode == null) { + return 0; + } + var transformer = TRANSFORMERS.get(classType.getClassName()); + if (transformer == null) { + return 0; + } + return transformer.transform(classNode); + } + + public interface Transformer { + int transform(ClassNode node); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/core/launchplugin/transformer/CapabilityProviderTransformer.java b/src/main/java/org/embeddedt/modernfix/core/launchplugin/transformer/CapabilityProviderTransformer.java new file mode 100644 index 00000000..d02da71a --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/core/launchplugin/transformer/CapabilityProviderTransformer.java @@ -0,0 +1,102 @@ +package org.embeddedt.modernfix.core.launchplugin.transformer; + +import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; +import org.embeddedt.modernfix.core.launchplugin.CoreLaunchPluginService; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; + +/** + * Injects an early-return into {@code CapabilityProvider#areCapsCompatible} that skips lazy + * initialization when both providers are lazy, uninitialized, of the same class, and carry equal + * {@code lazyData}. In that case the capabilities are trivially compatible and the full init can + * be avoided. + * @author embeddedt + */ +public class CapabilityProviderTransformer implements CoreLaunchPluginService.Transformer { + private static final String OWNER = "net/minecraftforge/common/capabilities/CapabilityProvider"; + private static final String COMPOUND_TAG = "net/minecraft/nbt/CompoundTag"; + private static final String HELPER_NAME = "mfix$skipLazyInit"; + private static final String HELPER_DESC = "(L" + OWNER + ";L" + OWNER + ";)Z"; + + @Override + public int transform(ClassNode node) { + String targetDesc = "(L" + OWNER + ";)Z"; + for (MethodNode method : node.methods) { + if (method.name.equals("areCapsCompatible") && method.desc.equals(targetDesc)) { + injectCall(method); + node.methods.add(buildHelper()); + break; + } + } + return ILaunchPluginService.ComputeFlags.COMPUTE_FRAMES; + } + + private MethodNode buildHelper() { + MethodNode mn = new MethodNode( + Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, + HELPER_NAME, HELPER_DESC, null, null); + InsnList il = mn.instructions; + LabelNode returnFalse = new LabelNode(); + + // if (!self.isLazy) goto returnFalse + il.add(new VarInsnNode(Opcodes.ALOAD, 0)); + il.add(new FieldInsnNode(Opcodes.GETFIELD, OWNER, "isLazy", "Z")); + il.add(new JumpInsnNode(Opcodes.IFEQ, returnFalse)); + + // if (self.initialized) goto returnFalse + il.add(new VarInsnNode(Opcodes.ALOAD, 0)); + il.add(new FieldInsnNode(Opcodes.GETFIELD, OWNER, "initialized", "Z")); + il.add(new JumpInsnNode(Opcodes.IFNE, returnFalse)); + + // if (!other.isLazy) goto returnFalse + il.add(new VarInsnNode(Opcodes.ALOAD, 1)); + il.add(new FieldInsnNode(Opcodes.GETFIELD, OWNER, "isLazy", "Z")); + il.add(new JumpInsnNode(Opcodes.IFEQ, returnFalse)); + + // if (other.initialized) goto returnFalse + il.add(new VarInsnNode(Opcodes.ALOAD, 1)); + il.add(new FieldInsnNode(Opcodes.GETFIELD, OWNER, "initialized", "Z")); + il.add(new JumpInsnNode(Opcodes.IFNE, returnFalse)); + + // if (other.getClass() != self.getClass()) goto returnFalse + il.add(new VarInsnNode(Opcodes.ALOAD, 1)); + il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false)); + il.add(new VarInsnNode(Opcodes.ALOAD, 0)); + il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false)); + il.add(new JumpInsnNode(Opcodes.IF_ACMPNE, returnFalse)); + + // if (!Objects.equals(self.lazyData, other.lazyData)) goto returnFalse + il.add(new VarInsnNode(Opcodes.ALOAD, 0)); + il.add(new FieldInsnNode(Opcodes.GETFIELD, OWNER, "lazyData", "L" + COMPOUND_TAG + ";")); + il.add(new VarInsnNode(Opcodes.ALOAD, 1)); + il.add(new FieldInsnNode(Opcodes.GETFIELD, OWNER, "lazyData", "L" + COMPOUND_TAG + ";")); + il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/Objects", "equals", "(Ljava/lang/Object;Ljava/lang/Object;)Z", false)); + il.add(new JumpInsnNode(Opcodes.IFEQ, returnFalse)); + + il.add(new InsnNode(Opcodes.ICONST_1)); + il.add(new InsnNode(Opcodes.IRETURN)); + + il.add(returnFalse); + il.add(new InsnNode(Opcodes.ICONST_0)); + il.add(new InsnNode(Opcodes.IRETURN)); + + mn.maxLocals = 2; + mn.maxStack = 2; + return mn; + } + + private void injectCall(MethodNode method) { + InsnList il = new InsnList(); + LabelNode skip = new LabelNode(); + + il.add(new VarInsnNode(Opcodes.ALOAD, 0)); + il.add(new VarInsnNode(Opcodes.ALOAD, 1)); + il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, OWNER, HELPER_NAME, HELPER_DESC, false)); + il.add(new JumpInsnNode(Opcodes.IFEQ, skip)); + il.add(new InsnNode(Opcodes.ICONST_1)); + il.add(new InsnNode(Opcodes.IRETURN)); + il.add(skip); + + method.instructions.insert(il); + } +}