Replace CapabilityProvider mixin with ASM transformer
Works around this Mixin bug: https://github.com/FabricMC/Mixin/issues/146 Since CapabilityProvider is the parent of many commonly targeted classes like Level, ItemStack, etc., this breaks mods Fixes #650
This commit is contained in:
parent
4e3ecf9b6d
commit
c73cdc49a4
|
|
@ -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<Boolean> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,6 +5,7 @@ import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig;
|
import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig;
|
||||||
import org.embeddedt.modernfix.core.config.Option;
|
import org.embeddedt.modernfix.core.config.Option;
|
||||||
|
import org.embeddedt.modernfix.core.launchplugin.CoreLaunchPluginService;
|
||||||
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||||
import org.embeddedt.modernfix.world.ThreadDumper;
|
import org.embeddedt.modernfix.world.ThreadDumper;
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
@ -109,7 +110,7 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoad(String mixinPackage) {
|
public void onLoad(String mixinPackage) {
|
||||||
|
CoreLaunchPluginService.install();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -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<String, ILaunchPluginService> plugins = (Map<String, ILaunchPluginService>)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<Phase> GO = EnumSet.of(Phase.AFTER);
|
||||||
|
private static final EnumSet<Phase> NOGO = EnumSet.noneOf(Phase.class);
|
||||||
|
|
||||||
|
private static final Map<String, Transformer> TRANSFORMERS = Map.of(
|
||||||
|
"net.minecraftforge.common.capabilities.CapabilityProvider", new CapabilityProviderTransformer()
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EnumSet<Phase> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user