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;
|
||||
|
||||
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.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.Recipe;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.craftbukkit.v.entity.CraftHumanEntity;
|
||||
|
@ -10,28 +10,24 @@ import org.bukkit.entity.HumanEntity;
|
|||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(Container.class)
|
||||
public interface ContainerMixin extends IInventoryBridge {
|
||||
|
||||
@Override
|
||||
default List<ItemStack> getContents() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void onOpen(CraftHumanEntity who) {
|
||||
SideViewingTracker.onOpen((Container) this, who);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void onClose(CraftHumanEntity who) {
|
||||
SideViewingTracker.onClose((Container) this, who);
|
||||
}
|
||||
|
||||
@Override
|
||||
default List<HumanEntity> getViewers() {
|
||||
return new ArrayList<>();
|
||||
return SideViewingTracker.getViewers((Container) this);
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
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.MarkerManager;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FieldInsnNode;
|
||||
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.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import org.objectweb.asm.tree.TypeInsnNode;
|
||||
import org.objectweb.asm.tree.VarInsnNode;
|
||||
|
||||
import java.io.InputStream;
|
||||
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 {
|
||||
|
||||
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 final Map<String, Integer> map = new ConcurrentHashMap<>();
|
||||
|
||||
public InventoryImplementer() {
|
||||
map.put(INV_TYPE, 1);
|
||||
map.put("java/lang/Object", 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
if (Modifier.isInterface(node.access) || node.interfaces.contains(BRIDGE_TYPE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return tryImplement(node);
|
||||
}
|
||||
|
||||
private boolean tryImplement(ClassNode node) {
|
||||
Set<String> methods = new HashSet<>();
|
||||
MethodNode stackLimitMethod = null;
|
||||
for (MethodNode method : node.methods) {
|
||||
String desc = method.name + method.desc;
|
||||
methods.add(desc);
|
||||
if (desc.equals("m_6893_()I")) {
|
||||
if (!Modifier.isAbstract(method.access) && method.name.equals("m_6893_") && method.desc.equals("()I")) { // getMaxStackSize
|
||||
stackLimitMethod = method;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (methods.contains("getViewers()Ljava/util/List;")) {
|
||||
ArclightImplementer.LOGGER.debug(MARKER, "Found implemented class {}", node.name);
|
||||
node.interfaces.add(BRIDGE_TYPE);
|
||||
if (stackLimitMethod == null) {
|
||||
return false;
|
||||
} else {
|
||||
ArclightImplementer.LOGGER.debug(MARKER, "Implementing inventory for class {} in {}", node.name, map.get(node.name));
|
||||
FieldNode transaction = new FieldNode(Opcodes.ACC_PRIVATE, "$transaction", Type.getType(List.class).getDescriptor(), null, null);
|
||||
FieldNode maxStack = new FieldNode(Opcodes.ACC_PRIVATE, "$maxStack", Type.getType(Integer.class).getDescriptor(), null, null);
|
||||
node.fields.add(transaction);
|
||||
for (MethodNode method : node.methods) {
|
||||
if (method.name.equals("setMaxStackSize") && method.desc.equals("(I)V")) {
|
||||
ArclightImplementer.LOGGER.debug(MARKER, "Found implemented class {}", node.name);
|
||||
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.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();
|
||||
LabelNode labelNode = new LabelNode();
|
||||
list.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
||||
|
@ -172,24 +66,7 @@ public class InventoryImplementer implements Implementer {
|
|||
list.add(new InsnNode(Opcodes.IRETURN));
|
||||
list.add(labelNode);
|
||||
list.add(new InsnNode(Opcodes.POP));
|
||||
if (stackLimitMethod != null && !Modifier.isAbstract(stackLimitMethod.access)) {
|
||||
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);
|
||||
}
|
||||
stackLimitMethod.instructions.insert(list);
|
||||
{
|
||||
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "setMaxStackSize", "(I)V", null, null);
|
||||
InsnList insnList = new InsnList();
|
||||
|
@ -201,23 +78,6 @@ public class InventoryImplementer implements Implementer {
|
|||
methodNode.instructions = insnList;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user