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.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
|
||||
|
|
|
|||
|
|
@ -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