Remove inheritance lookup in inventory implementer
Inheritance lookup is a fairly expensive operation, using a side map for viewers lookup can improve performance.
This commit is contained in:
parent
0f76fd6de8
commit
fb4b63ec08
|
@ -1,8 +1,8 @@
|
||||||
package io.izzel.arclight.common.mixin.core.world;
|
package io.izzel.arclight.common.mixin.core.world;
|
||||||
|
|
||||||
import io.izzel.arclight.common.bridge.core.inventory.IInventoryBridge;
|
import io.izzel.arclight.common.bridge.core.inventory.IInventoryBridge;
|
||||||
|
import io.izzel.arclight.common.mod.inventory.SideViewingTracker;
|
||||||
import net.minecraft.world.Container;
|
import net.minecraft.world.Container;
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
import net.minecraft.world.item.crafting.Recipe;
|
import net.minecraft.world.item.crafting.Recipe;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.craftbukkit.v.entity.CraftHumanEntity;
|
import org.bukkit.craftbukkit.v.entity.CraftHumanEntity;
|
||||||
|
@ -10,28 +10,24 @@ import org.bukkit.entity.HumanEntity;
|
||||||
import org.bukkit.inventory.InventoryHolder;
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Mixin(Container.class)
|
@Mixin(Container.class)
|
||||||
public interface ContainerMixin extends IInventoryBridge {
|
public interface ContainerMixin extends IInventoryBridge {
|
||||||
|
|
||||||
@Override
|
|
||||||
default List<ItemStack> getContents() {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void onOpen(CraftHumanEntity who) {
|
default void onOpen(CraftHumanEntity who) {
|
||||||
|
SideViewingTracker.onOpen((Container) this, who);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void onClose(CraftHumanEntity who) {
|
default void onClose(CraftHumanEntity who) {
|
||||||
|
SideViewingTracker.onClose((Container) this, who);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default List<HumanEntity> getViewers() {
|
default List<HumanEntity> getViewers() {
|
||||||
return new ArrayList<>();
|
return SideViewingTracker.getViewers((Container) this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package io.izzel.arclight.common.mod.inventory;
|
||||||
|
|
||||||
|
import net.minecraft.world.Container;
|
||||||
|
import org.bukkit.entity.HumanEntity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
public class SideViewingTracker {
|
||||||
|
|
||||||
|
private static final Map<Container, List<HumanEntity>> VIEWERS = Collections.synchronizedMap(new WeakHashMap<>());
|
||||||
|
|
||||||
|
public static void onOpen(Container container, HumanEntity humanEntity) {
|
||||||
|
VIEWERS.computeIfAbsent(container, k -> new ArrayList<>()).add(humanEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onClose(Container container, HumanEntity humanEntity) {
|
||||||
|
VIEWERS.computeIfAbsent(container, k -> new ArrayList<>()).remove(humanEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<HumanEntity> getViewers(Container container) {
|
||||||
|
return VIEWERS.computeIfAbsent(container, k -> new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,10 @@
|
||||||
package io.izzel.arclight.boot.asm;
|
package io.izzel.arclight.boot.asm;
|
||||||
|
|
||||||
import com.google.common.io.ByteStreams;
|
|
||||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||||
import io.izzel.arclight.api.ArclightVersion;
|
|
||||||
import io.izzel.arclight.i18n.LocalizedException;
|
|
||||||
import org.apache.logging.log4j.Marker;
|
import org.apache.logging.log4j.Marker;
|
||||||
import org.apache.logging.log4j.MarkerManager;
|
import org.apache.logging.log4j.MarkerManager;
|
||||||
import org.objectweb.asm.ClassReader;
|
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.ClassNode;
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
import org.objectweb.asm.tree.FieldInsnNode;
|
import org.objectweb.asm.tree.FieldInsnNode;
|
||||||
import org.objectweb.asm.tree.FieldNode;
|
import org.objectweb.asm.tree.FieldNode;
|
||||||
|
@ -19,149 +14,48 @@ import org.objectweb.asm.tree.JumpInsnNode;
|
||||||
import org.objectweb.asm.tree.LabelNode;
|
import org.objectweb.asm.tree.LabelNode;
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
import org.objectweb.asm.tree.MethodInsnNode;
|
||||||
import org.objectweb.asm.tree.MethodNode;
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
import org.objectweb.asm.tree.TypeInsnNode;
|
|
||||||
import org.objectweb.asm.tree.VarInsnNode;
|
import org.objectweb.asm.tree.VarInsnNode;
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public class InventoryImplementer implements Implementer {
|
public class InventoryImplementer implements Implementer {
|
||||||
|
|
||||||
private static final Marker MARKER = MarkerManager.getMarker("INVENTORY");
|
private static final Marker MARKER = MarkerManager.getMarker("INVENTORY");
|
||||||
private static final String INV_TYPE = "net/minecraft/world/Container";
|
|
||||||
private static final String BRIDGE_TYPE = "io/izzel/arclight/common/bridge/core/inventory/IInventoryBridge";
|
private static final String BRIDGE_TYPE = "io/izzel/arclight/common/bridge/core/inventory/IInventoryBridge";
|
||||||
|
|
||||||
private final Map<String, Integer> map = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public InventoryImplementer() {
|
public InventoryImplementer() {
|
||||||
map.put(INV_TYPE, 1);
|
|
||||||
map.put("java/lang/Object", 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) {
|
public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) {
|
||||||
try {
|
if (Modifier.isInterface(node.access) || node.interfaces.contains(BRIDGE_TYPE)) {
|
||||||
if (Modifier.isInterface(node.access) || node.interfaces.contains(BRIDGE_TYPE)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isInventoryClass(node, transformerLoader)) {
|
|
||||||
return tryImplement(node);
|
|
||||||
} else return false;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
if (t instanceof LocalizedException) {
|
|
||||||
ArclightImplementer.LOGGER.error(MARKER, ((LocalizedException) t).node(), ((LocalizedException) t).args());
|
|
||||||
} else {
|
|
||||||
ArclightImplementer.LOGGER.error(MARKER, "Error processing class", t);
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
return tryImplement(node);
|
||||||
|
|
||||||
private boolean isInventoryClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) throws Throwable {
|
|
||||||
if (node == null) { // maybe runtime defined class
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Integer ret = map.get(node.name);
|
|
||||||
if (ret != null) return ret > 1;
|
|
||||||
Integer i = map.get(node.superName);
|
|
||||||
if (i != null) {
|
|
||||||
if (i > 1) {
|
|
||||||
map.put(node.name, i + 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (node.interfaces.contains(INV_TYPE)) {
|
|
||||||
map.put(node.name, 2);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
boolean b = isInventoryClass(findClass(node.superName, transformerLoader), transformerLoader);
|
|
||||||
if (b) {
|
|
||||||
map.put(node.name, map.get(node.superName) + 1);
|
|
||||||
} else {
|
|
||||||
map.put(node.name, 0);
|
|
||||||
}
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClassNode findClass(String typeName, ILaunchPluginService.ITransformerLoader transformerLoader) throws Exception {
|
|
||||||
try {
|
|
||||||
byte[] array;
|
|
||||||
InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(typeName + ".class");
|
|
||||||
if (stream != null) {
|
|
||||||
array = ByteStreams.toByteArray(stream);
|
|
||||||
} else {
|
|
||||||
array = transformerLoader.buildTransformedClassNodeFor(Type.getObjectType(typeName).getClassName());
|
|
||||||
}
|
|
||||||
ClassNode node = new ClassNode();
|
|
||||||
new ClassReader(array).accept(node, ClassReader.SKIP_CODE);
|
|
||||||
return node;
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
ArclightImplementer.LOGGER.debug("implementer.not-found", typeName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean tryImplement(ClassNode node) {
|
private boolean tryImplement(ClassNode node) {
|
||||||
Set<String> methods = new HashSet<>();
|
|
||||||
MethodNode stackLimitMethod = null;
|
MethodNode stackLimitMethod = null;
|
||||||
for (MethodNode method : node.methods) {
|
for (MethodNode method : node.methods) {
|
||||||
String desc = method.name + method.desc;
|
if (!Modifier.isAbstract(method.access) && method.name.equals("m_6893_") && method.desc.equals("()I")) { // getMaxStackSize
|
||||||
methods.add(desc);
|
|
||||||
if (desc.equals("m_6893_()I")) {
|
|
||||||
stackLimitMethod = method;
|
stackLimitMethod = method;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (methods.contains("getViewers()Ljava/util/List;")) {
|
if (stackLimitMethod == null) {
|
||||||
ArclightImplementer.LOGGER.debug(MARKER, "Found implemented class {}", node.name);
|
|
||||||
node.interfaces.add(BRIDGE_TYPE);
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
ArclightImplementer.LOGGER.debug(MARKER, "Implementing inventory for class {} in {}", node.name, map.get(node.name));
|
for (MethodNode method : node.methods) {
|
||||||
FieldNode transaction = new FieldNode(Opcodes.ACC_PRIVATE, "$transaction", Type.getType(List.class).getDescriptor(), null, null);
|
if (method.name.equals("setMaxStackSize") && method.desc.equals("(I)V")) {
|
||||||
FieldNode maxStack = new FieldNode(Opcodes.ACC_PRIVATE, "$maxStack", Type.getType(Integer.class).getDescriptor(), null, null);
|
ArclightImplementer.LOGGER.debug(MARKER, "Found implemented class {}", node.name);
|
||||||
node.fields.add(transaction);
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ArclightImplementer.LOGGER.debug(MARKER, "Implementing inventory for class {}", node.name);
|
||||||
|
FieldNode maxStack = new FieldNode(Opcodes.ACC_PRIVATE, "arclight$maxStack", Type.getType(Integer.class).getDescriptor(), null, null);
|
||||||
node.fields.add(maxStack);
|
node.fields.add(maxStack);
|
||||||
node.interfaces.add(BRIDGE_TYPE);
|
node.interfaces.add(BRIDGE_TYPE);
|
||||||
{
|
|
||||||
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "onOpen", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getObjectType("org/bukkit/craftbukkit/" + ArclightVersion.current().packageName() + "/entity/CraftHumanEntity")), null, null);
|
|
||||||
InsnList insnList = new InsnList();
|
|
||||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
|
||||||
insnList.add(new FieldInsnNode(Opcodes.GETFIELD, node.name, transaction.name, transaction.desc));
|
|
||||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, 1));
|
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "add", "(Ljava/lang/Object;)Z", true));
|
|
||||||
insnList.add(new InsnNode(Opcodes.POP));
|
|
||||||
insnList.add(new InsnNode(Opcodes.RETURN));
|
|
||||||
methodNode.instructions = insnList;
|
|
||||||
node.methods.add(methodNode);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "onClose", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getObjectType("org/bukkit/craftbukkit/" + ArclightVersion.current().packageName() + "/entity/CraftHumanEntity")), null, null);
|
|
||||||
InsnList insnList = new InsnList();
|
|
||||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
|
||||||
insnList.add(new FieldInsnNode(Opcodes.GETFIELD, node.name, transaction.name, transaction.desc));
|
|
||||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, 1));
|
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "remove", "(Ljava/lang/Object;)Z", true));
|
|
||||||
insnList.add(new InsnNode(Opcodes.POP));
|
|
||||||
insnList.add(new InsnNode(Opcodes.RETURN));
|
|
||||||
methodNode.instructions = insnList;
|
|
||||||
node.methods.add(methodNode);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "getViewers", Type.getMethodDescriptor(Type.getType(List.class)), null, null);
|
|
||||||
InsnList insnList = new InsnList();
|
|
||||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
|
||||||
insnList.add(new FieldInsnNode(Opcodes.GETFIELD, node.name, transaction.name, transaction.desc));
|
|
||||||
insnList.add(new InsnNode(Opcodes.ARETURN));
|
|
||||||
methodNode.instructions = insnList;
|
|
||||||
node.methods.add(methodNode);
|
|
||||||
}
|
|
||||||
InsnList list = new InsnList();
|
InsnList list = new InsnList();
|
||||||
LabelNode labelNode = new LabelNode();
|
LabelNode labelNode = new LabelNode();
|
||||||
list.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
list.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
||||||
|
@ -172,24 +66,7 @@ public class InventoryImplementer implements Implementer {
|
||||||
list.add(new InsnNode(Opcodes.IRETURN));
|
list.add(new InsnNode(Opcodes.IRETURN));
|
||||||
list.add(labelNode);
|
list.add(labelNode);
|
||||||
list.add(new InsnNode(Opcodes.POP));
|
list.add(new InsnNode(Opcodes.POP));
|
||||||
if (stackLimitMethod != null && !Modifier.isAbstract(stackLimitMethod.access)) {
|
stackLimitMethod.instructions.insert(list);
|
||||||
stackLimitMethod.instructions.insert(list);
|
|
||||||
} else {
|
|
||||||
MethodNode methodNode = stackLimitMethod == null
|
|
||||||
? new MethodNode(0, "m_6893_()I", "()I", null, null)
|
|
||||||
: stackLimitMethod;
|
|
||||||
methodNode.access = Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC;
|
|
||||||
int level = map.get(node.name);
|
|
||||||
list.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
|
||||||
if (level > 2) {
|
|
||||||
list.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, node.superName, methodNode.name, methodNode.desc, false));
|
|
||||||
} else {
|
|
||||||
list.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, INV_TYPE, methodNode.name, methodNode.desc, true));
|
|
||||||
}
|
|
||||||
list.add(new InsnNode(Opcodes.IRETURN));
|
|
||||||
methodNode.instructions.insert(list);
|
|
||||||
node.methods.add(methodNode);
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "setMaxStackSize", "(I)V", null, null);
|
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "setMaxStackSize", "(I)V", null, null);
|
||||||
InsnList insnList = new InsnList();
|
InsnList insnList = new InsnList();
|
||||||
|
@ -201,23 +78,6 @@ public class InventoryImplementer implements Implementer {
|
||||||
methodNode.instructions = insnList;
|
methodNode.instructions = insnList;
|
||||||
node.methods.add(methodNode);
|
node.methods.add(methodNode);
|
||||||
}
|
}
|
||||||
{
|
|
||||||
for (MethodNode methodNode : node.methods) {
|
|
||||||
if (methodNode.name.equals("<init>")) {
|
|
||||||
AbstractInsnNode initNode = methodNode.instructions.getFirst();
|
|
||||||
while (!(initNode.getOpcode() == Opcodes.INVOKESPECIAL && ((MethodInsnNode) initNode).name.equals("<init>"))) {
|
|
||||||
initNode = initNode.getNext();
|
|
||||||
}
|
|
||||||
InsnList insnList = new InsnList();
|
|
||||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
|
||||||
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(ArrayList.class)));
|
|
||||||
insnList.add(new InsnNode(Opcodes.DUP));
|
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(ArrayList.class), "<init>", "()V", false));
|
|
||||||
insnList.add(new FieldInsnNode(Opcodes.PUTFIELD, node.name, transaction.name, transaction.desc));
|
|
||||||
methodNode.instructions.insert(initNode, insnList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user