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:
IzzelAliz 2022-09-03 23:22:35 +08:00
parent 0f76fd6de8
commit fb4b63ec08
No known key found for this signature in database
GPG Key ID: EE50E123A11D8338
3 changed files with 46 additions and 163 deletions

View File

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

View File

@ -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<>());
}
}

View File

@ -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;
}
}