Port async catcher
This commit is contained in:
parent
9c359490aa
commit
fcc143a24f
|
@ -7,7 +7,7 @@ A Bukkit server implementation utilizing Mixin.
|
||||||
| Minecraft | Forge | Status | Build |
|
| Minecraft | Forge | Status | Build |
|
||||||
| :----: | :----: | :---: | :---: |
|
| :----: | :----: | :---: | :---: |
|
||||||
| 1.16.x | 34.1.7 | ACTIVE | [![1.16 Status](https://img.shields.io/appveyor/build/IzzelAliz/arclight-16?style=flat-square)](https://ci.appveyor.com/project/IzzelAliz/arclight-16) |
|
| 1.16.x | 34.1.7 | ACTIVE | [![1.16 Status](https://img.shields.io/appveyor/build/IzzelAliz/arclight-16?style=flat-square)](https://ci.appveyor.com/project/IzzelAliz/arclight-16) |
|
||||||
| 1.15.x | 31.2.37 | ACTIVE | [![1.15 Status](https://img.shields.io/appveyor/build/IzzelAliz/arclight-15?style=flat-square)](https://ci.appveyor.com/project/IzzelAliz/arclight-15) |
|
| 1.15.x | 31.2.45 | ACTIVE | [![1.15 Status](https://img.shields.io/appveyor/build/IzzelAliz/arclight-15?style=flat-square)](https://ci.appveyor.com/project/IzzelAliz/arclight-15) |
|
||||||
| 1.14.x | 28.2.0 | [LEGACY](https://github.com/IzzelAliz/Arclight/releases/tag/1.0.6) | [![1.14 Status](https://img.shields.io/appveyor/build/IzzelAliz/arclight?style=flat-square)](https://ci.appveyor.com/project/IzzelAliz/arclight) |
|
| 1.14.x | 28.2.0 | [LEGACY](https://github.com/IzzelAliz/Arclight/releases/tag/1.0.6) | [![1.14 Status](https://img.shields.io/appveyor/build/IzzelAliz/arclight?style=flat-square)](https://ci.appveyor.com/project/IzzelAliz/arclight) |
|
||||||
|
|
||||||
* Legacy version still accepts pull requests.
|
* Legacy version still accepts pull requests.
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.izzel.arclight.api;
|
||||||
|
|
||||||
import sun.reflect.CallerSensitive;
|
import sun.reflect.CallerSensitive;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
|
@ -11,6 +12,7 @@ public class Unsafe {
|
||||||
|
|
||||||
private static final sun.misc.Unsafe unsafe;
|
private static final sun.misc.Unsafe unsafe;
|
||||||
private static final MethodHandles.Lookup lookup;
|
private static final MethodHandles.Lookup lookup;
|
||||||
|
private static final MethodHandle defineClass;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
|
@ -22,11 +24,24 @@ public class Unsafe {
|
||||||
Object base = unsafe.staticFieldBase(field);
|
Object base = unsafe.staticFieldBase(field);
|
||||||
long offset = unsafe.staticFieldOffset(field);
|
long offset = unsafe.staticFieldOffset(field);
|
||||||
lookup = (MethodHandles.Lookup) unsafe.getObject(base, offset);
|
lookup = (MethodHandles.Lookup) unsafe.getObject(base, offset);
|
||||||
|
defineClass = lookup.unreflect(ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ProtectionDomain.class));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> T getStatic(Class<?> cl, String name) {
|
||||||
|
try {
|
||||||
|
Unsafe.ensureClassInitialized(cl);
|
||||||
|
Field field = cl.getDeclaredField(name);
|
||||||
|
Object materialByNameBase = Unsafe.staticFieldBase(field);
|
||||||
|
long materialByNameOffset = Unsafe.staticFieldOffset(field);
|
||||||
|
return (T) Unsafe.getObject(materialByNameBase, materialByNameOffset);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static MethodHandles.Lookup lookup() {
|
public static MethodHandles.Lookup lookup() {
|
||||||
return lookup;
|
return lookup;
|
||||||
}
|
}
|
||||||
|
@ -337,7 +352,12 @@ public class Unsafe {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Class<?> defineClass(String s, byte[] bytes, int i, int i1, ClassLoader classLoader, ProtectionDomain protectionDomain) {
|
public static Class<?> defineClass(String s, byte[] bytes, int i, int i1, ClassLoader classLoader, ProtectionDomain protectionDomain) {
|
||||||
return unsafe.defineClass(s, bytes, i, i1, classLoader, protectionDomain);
|
try {
|
||||||
|
return (Class<?>) defineClass.bindTo(classLoader).invoke(s, bytes, i , i1, protectionDomain);
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
throwException(throwable);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Class<?> defineAnonymousClass(Class<?> aClass, byte[] bytes, Object[] objects) {
|
public static Class<?> defineAnonymousClass(Class<?> aClass, byte[] bytes, Object[] objects) {
|
||||||
|
|
|
@ -3,10 +3,15 @@ package io.izzel.arclight.common.asm;
|
||||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||||
import io.izzel.arclight.common.mod.util.log.ArclightI18nLogger;
|
import io.izzel.arclight.common.mod.util.log.ArclightI18nLogger;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.tree.ClassNode;
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.objectweb.asm.tree.InsnList;
|
||||||
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
import org.objectweb.asm.tree.VarInsnNode;
|
||||||
import org.spongepowered.asm.launch.MixinLaunchPlugin;
|
import org.spongepowered.asm.launch.MixinLaunchPlugin;
|
||||||
|
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
@ -36,6 +41,7 @@ public class ArclightImplementer implements ILaunchPluginService {
|
||||||
this.transformerLoader = transformerLoader;
|
this.transformerLoader = transformerLoader;
|
||||||
this.implementers.put("inventory", new InventoryImplementer());
|
this.implementers.put("inventory", new InventoryImplementer());
|
||||||
this.implementers.put("switch", SwitchTableFixer.INSTANCE);
|
this.implementers.put("switch", SwitchTableFixer.INSTANCE);
|
||||||
|
this.implementers.put("async", AsyncCatcher.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -79,4 +85,15 @@ public class ArclightImplementer implements ILaunchPluginService {
|
||||||
public boolean processClass(Phase phase, ClassNode classNode, Type classType) {
|
public boolean processClass(Phase phase, ClassNode classNode, Type classType) {
|
||||||
throw new IllegalStateException("Outdated ModLauncher");
|
throw new IllegalStateException("Outdated ModLauncher");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void loadArgs(InsnList list, MethodNode methodNode, Type[] types, int i) {
|
||||||
|
if (!Modifier.isStatic(methodNode.access)) {
|
||||||
|
list.add(new VarInsnNode(Opcodes.ALOAD, i));
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
for (Type type : types) {
|
||||||
|
list.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), i));
|
||||||
|
i += type.getSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,255 @@
|
||||||
|
package io.izzel.arclight.common.asm;
|
||||||
|
|
||||||
|
import com.google.common.reflect.TypeToken;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||||
|
import io.izzel.arclight.api.Unsafe;
|
||||||
|
import io.izzel.arclight.common.mod.server.ArclightServer;
|
||||||
|
import io.izzel.arclight.i18n.ArclightConfig;
|
||||||
|
import io.izzel.arclight.i18n.conf.AsyncCatcherSpec;
|
||||||
|
import org.apache.logging.log4j.Marker;
|
||||||
|
import org.apache.logging.log4j.MarkerManager;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.Label;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.commons.GeneratorAdapter;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.objectweb.asm.tree.FieldInsnNode;
|
||||||
|
import org.objectweb.asm.tree.FieldNode;
|
||||||
|
import org.objectweb.asm.tree.InsnList;
|
||||||
|
import org.objectweb.asm.tree.InsnNode;
|
||||||
|
import org.objectweb.asm.tree.JumpInsnNode;
|
||||||
|
import org.objectweb.asm.tree.LabelNode;
|
||||||
|
import org.objectweb.asm.tree.LdcInsnNode;
|
||||||
|
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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
import org.spongepowered.asm.util.Constants;
|
||||||
|
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class AsyncCatcher implements Implementer {
|
||||||
|
|
||||||
|
public static final AsyncCatcher INSTANCE = new AsyncCatcher();
|
||||||
|
private static final Marker MARKER = MarkerManager.getMarker("ASYNC_CATCHER");
|
||||||
|
private static final CallbackInfoReturnable<?> NOOP = new CallbackInfoReturnable<>("noop", false);
|
||||||
|
private static final AtomicInteger COUNTER = new AtomicInteger(0);
|
||||||
|
|
||||||
|
private final boolean dump;
|
||||||
|
private final boolean warn;
|
||||||
|
private final AsyncCatcherSpec.Operation defaultOp;
|
||||||
|
private final Map<String, Map<String, String>> reasons;
|
||||||
|
private final ClassLoader classLoader;
|
||||||
|
|
||||||
|
public AsyncCatcher() {
|
||||||
|
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
|
||||||
|
this.reasons = gson.fromJson(
|
||||||
|
new InputStreamReader(AsyncCatcher.class.getResourceAsStream("/async_catcher.json")),
|
||||||
|
new TypeToken<Map<String, Map<String, String>>>() {}.getType()
|
||||||
|
);
|
||||||
|
this.defaultOp = ArclightConfig.spec().getAsyncCatcher().getDefaultOp();
|
||||||
|
this.dump = ArclightConfig.spec().getAsyncCatcher().isDump();
|
||||||
|
this.warn = ArclightConfig.spec().getAsyncCatcher().isWarn();
|
||||||
|
this.classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) {
|
||||||
|
Map<String, String> map = reasons.get(node.name);
|
||||||
|
if (map != null) {
|
||||||
|
boolean found = false;
|
||||||
|
List<MethodNode> methods = node.methods;
|
||||||
|
for (int i = 0, methodsSize = methods.size(); i < methodsSize; i++) {
|
||||||
|
MethodNode method = methods.get(i);
|
||||||
|
String reason = map.get(method.name + method.desc);
|
||||||
|
if (reason != null) {
|
||||||
|
found = true;
|
||||||
|
injectCheck(node, method, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void injectCheck(ClassNode node, MethodNode methodNode, String reason) {
|
||||||
|
ArclightImplementer.LOGGER.debug(MARKER, "Injecting {}/{}{} for reason {}", node.name, methodNode.name, methodNode.desc, reason);
|
||||||
|
AsyncCatcherSpec.Operation operation = ArclightConfig.spec().getAsyncCatcher().getOverrides().getOrDefault(reason, defaultOp);
|
||||||
|
InsnList insnList = new InsnList();
|
||||||
|
LabelNode labelNode = new LabelNode(new Label());
|
||||||
|
LabelNode labelNode1 = new LabelNode(new Label());
|
||||||
|
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "io/izzel/arclight/common/mod/server/ArclightServer", "isPrimaryThread", "()Z"));
|
||||||
|
insnList.add(new JumpInsnNode(Opcodes.IFNE, labelNode));
|
||||||
|
instantiateCallback(node, methodNode, insnList);
|
||||||
|
insnList.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getType(AsyncCatcherSpec.Operation.class).getInternalName(), operation.name(), Type.getType(AsyncCatcherSpec.Operation.class).getDescriptor()));
|
||||||
|
insnList.add(new LdcInsnNode(reason));
|
||||||
|
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getType(AsyncCatcher.class).getInternalName(), "checkOp", "(Ljava/util/function/Supplier;Lio/izzel/arclight/i18n/conf/AsyncCatcherSpec$Operation;Ljava/lang/String;)Lorg/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable;"));
|
||||||
|
Type returnType = Type.getMethodType(methodNode.desc).getReturnType();
|
||||||
|
boolean hasReturn = !returnType.equals(Type.VOID_TYPE);
|
||||||
|
if (hasReturn) {
|
||||||
|
insnList.add(new InsnNode(Opcodes.DUP));
|
||||||
|
}
|
||||||
|
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getType(CallbackInfoReturnable.class).getInternalName(), "isCancelled", "()Z"));
|
||||||
|
insnList.add(new JumpInsnNode(Opcodes.IFEQ, hasReturn ? labelNode1 : labelNode));
|
||||||
|
if (hasReturn) {
|
||||||
|
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getType(CallbackInfoReturnable.class).getInternalName(), getReturnAccessor(returnType), getReturnDescriptor(returnType)));
|
||||||
|
if (returnType.getSort() > Type.DOUBLE) {
|
||||||
|
insnList.add(new TypeInsnNode(Opcodes.CHECKCAST, returnType.getInternalName()));
|
||||||
|
}
|
||||||
|
insnList.add(new InsnNode(returnType.getOpcode(Opcodes.IRETURN)));
|
||||||
|
} else {
|
||||||
|
insnList.add(new InsnNode(Opcodes.RETURN));
|
||||||
|
}
|
||||||
|
insnList.add(labelNode1);
|
||||||
|
insnList.add(new InsnNode(Opcodes.POP));
|
||||||
|
insnList.add(labelNode);
|
||||||
|
methodNode.instructions.insert(insnList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void instantiateCallback(ClassNode node, MethodNode methodNode, InsnList insnList) {
|
||||||
|
MethodNode bridge;
|
||||||
|
if (Modifier.isPrivate(methodNode.access)) {
|
||||||
|
bridge = createBridge(node, methodNode);
|
||||||
|
} else {
|
||||||
|
bridge = methodNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassNode classNode = new ClassNode();
|
||||||
|
String desc = createImplType(node, methodNode, classNode, bridge);
|
||||||
|
|
||||||
|
insnList.add(new TypeInsnNode(Opcodes.NEW, classNode.name));
|
||||||
|
insnList.add(new InsnNode(Opcodes.DUP));
|
||||||
|
Type methodType = Type.getMethodType(methodNode.desc);
|
||||||
|
ArclightImplementer.loadArgs(insnList, methodNode, methodType.getArgumentTypes(), 0);
|
||||||
|
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, classNode.name, "<init>", desc));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createImplType(ClassNode node, MethodNode methodNode, ClassNode classNode, MethodNode bridge) {
|
||||||
|
classNode.version = Opcodes.V1_8;
|
||||||
|
classNode.name = node.name + "$AsyncCatcher$" + COUNTER.getAndIncrement();
|
||||||
|
classNode.access = Opcodes.ACC_SYNTHETIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL;
|
||||||
|
classNode.superName = "java/lang/Object";
|
||||||
|
classNode.interfaces.add(Type.getType(Supplier.class).getInternalName());
|
||||||
|
List<Type> types = new ArrayList<>();
|
||||||
|
if (!Modifier.isStatic(methodNode.access)) {
|
||||||
|
types.add(Type.getObjectType(node.name));
|
||||||
|
}
|
||||||
|
types.addAll(Arrays.asList(Type.getArgumentTypes(methodNode.desc)));
|
||||||
|
|
||||||
|
for (int i = 0; i < types.size(); i++) {
|
||||||
|
FieldNode fieldNode = new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, "x" + i, types.get(i).getDescriptor(), null, null);
|
||||||
|
classNode.fields.add(fieldNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodNode init = new MethodNode();
|
||||||
|
init.name = "<init>";
|
||||||
|
init.desc = Type.getMethodType(Type.VOID_TYPE, types.toArray(new Type[0])).getDescriptor();
|
||||||
|
init.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
||||||
|
init.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V"));
|
||||||
|
|
||||||
|
int offset = 1;
|
||||||
|
for (int i = 0; i < types.size(); i++) {
|
||||||
|
init.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
||||||
|
init.instructions.add(new VarInsnNode(types.get(i).getOpcode(Opcodes.ILOAD), offset));
|
||||||
|
init.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, "x" + i, types.get(i).getDescriptor()));
|
||||||
|
offset += types.get(i).getSize();
|
||||||
|
}
|
||||||
|
init.instructions.add(new InsnNode(Opcodes.RETURN));
|
||||||
|
classNode.methods.add(init);
|
||||||
|
|
||||||
|
MethodNode get = new MethodNode();
|
||||||
|
get.name = "get";
|
||||||
|
get.desc = "()Ljava/lang/Object;";
|
||||||
|
GeneratorAdapter adapter = new GeneratorAdapter(get, Opcodes.ACC_PUBLIC, get.name, get.desc);
|
||||||
|
for (int i = 0; i < types.size(); i++) {
|
||||||
|
adapter.loadThis();
|
||||||
|
adapter.getField(Type.getObjectType(classNode.name), "x" + i, types.get(i));
|
||||||
|
}
|
||||||
|
get.instructions.add(new MethodInsnNode(
|
||||||
|
Modifier.isStatic(methodNode.access) ? Opcodes.INVOKESTATIC : Opcodes.INVOKEVIRTUAL,
|
||||||
|
node.name, bridge.name, bridge.desc
|
||||||
|
));
|
||||||
|
adapter.valueOf(Type.getReturnType(bridge.desc));
|
||||||
|
adapter.returnValue();
|
||||||
|
classNode.methods.add(get);
|
||||||
|
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||||
|
classNode.accept(writer);
|
||||||
|
byte[] bytes = writer.toByteArray();
|
||||||
|
Unsafe.defineClass(Type.getObjectType(classNode.name).getClassName(), bytes, 0, bytes.length, this.classLoader, AsyncCatcher.class.getProtectionDomain());
|
||||||
|
ArclightImplementer.LOGGER.debug(MARKER, "Defined impl callback class {}", classNode.name);
|
||||||
|
return init.desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodNode createBridge(ClassNode node, MethodNode methodNode) {
|
||||||
|
MethodNode ret = new MethodNode();
|
||||||
|
ret.name = methodNode.name + "$asyncCatcher$" + COUNTER.getAndIncrement();
|
||||||
|
ret.desc = methodNode.desc;
|
||||||
|
ret.access = Opcodes.ACC_PUBLIC | Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC;
|
||||||
|
if (Modifier.isStatic(methodNode.access)) {
|
||||||
|
ret.access = ret.access | Opcodes.ACC_STATIC;
|
||||||
|
}
|
||||||
|
Type methodType = Type.getMethodType(methodNode.desc);
|
||||||
|
ArclightImplementer.loadArgs(ret.instructions, methodNode, methodType.getArgumentTypes(), 0);
|
||||||
|
int invokeCode = Modifier.isStatic(methodNode.access) ? Opcodes.INVOKESTATIC : Opcodes.INVOKESPECIAL;
|
||||||
|
ret.instructions.add(new MethodInsnNode(invokeCode, node.name, methodNode.name, methodNode.desc));
|
||||||
|
ret.instructions.add(new InsnNode(methodType.getReturnType().getOpcode(Opcodes.IRETURN)));
|
||||||
|
node.methods.add(ret);
|
||||||
|
ArclightImplementer.LOGGER.debug(MARKER, "Bridge method {}/{}{} created", node.name, ret.name, ret.desc);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getReturnAccessor(org.objectweb.asm.Type returnType) {
|
||||||
|
if (returnType.getSort() == org.objectweb.asm.Type.OBJECT || returnType.getSort() == org.objectweb.asm.Type.ARRAY) {
|
||||||
|
return "getReturnValue";
|
||||||
|
}
|
||||||
|
return String.format("getReturnValue%s", returnType.getDescriptor());
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getReturnDescriptor(org.objectweb.asm.Type returnType) {
|
||||||
|
if (returnType.getSort() == org.objectweb.asm.Type.OBJECT || returnType.getSort() == org.objectweb.asm.Type.ARRAY) {
|
||||||
|
return String.format("()%s", Constants.OBJECT_DESC);
|
||||||
|
}
|
||||||
|
return String.format("()%s", returnType.getDescriptor());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> CallbackInfoReturnable<T> checkOp(Supplier<T> method, AsyncCatcherSpec.Operation operation, String reason) throws Throwable {
|
||||||
|
if (INSTANCE.warn) {
|
||||||
|
ArclightImplementer.LOGGER.warn(MARKER, "Async " + reason);
|
||||||
|
}
|
||||||
|
IllegalStateException exception = new IllegalStateException("Asynchronous " + reason + "!");
|
||||||
|
if (INSTANCE.dump) {
|
||||||
|
ArclightImplementer.LOGGER.debug(MARKER, "Async " + reason, exception);
|
||||||
|
}
|
||||||
|
switch (operation) {
|
||||||
|
case NONE: return (CallbackInfoReturnable<T>) NOOP;
|
||||||
|
case EXCEPTION: throw exception;
|
||||||
|
case BLOCK: {
|
||||||
|
CallbackInfoReturnable<T> cir = new CallbackInfoReturnable<>(reason, true);
|
||||||
|
CompletableFuture<T> future = CompletableFuture.supplyAsync(method, ArclightServer.getMainThreadExecutor());
|
||||||
|
cir.setReturnValue(future.get(5, TimeUnit.SECONDS));
|
||||||
|
return cir;
|
||||||
|
}
|
||||||
|
case DISPATCH: {
|
||||||
|
ArclightServer.executeOnMainThread(method::get);
|
||||||
|
CallbackInfoReturnable<T> cir = new CallbackInfoReturnable<>(reason, true);
|
||||||
|
cir.cancel();
|
||||||
|
return cir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("how this can happen?");
|
||||||
|
}
|
||||||
|
}
|
|
@ -295,6 +295,9 @@ public abstract class MinecraftServerMixin extends RecursiveEventLoop<TickDelaye
|
||||||
|
|
||||||
private void executeModerately() {
|
private void executeModerately() {
|
||||||
this.drainTasks();
|
this.drainTasks();
|
||||||
|
while (!processQueue.isEmpty()) {
|
||||||
|
processQueue.remove().run();
|
||||||
|
}
|
||||||
java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L);
|
java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,11 @@ import org.bukkit.craftbukkit.v.command.ColouredConsoleSender;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
public class ArclightServer {
|
public class ArclightServer {
|
||||||
|
|
||||||
|
private static final Executor mainThreadExecutor = ArclightServer::executeOnMainThread;
|
||||||
private static CraftServer server;
|
private static CraftServer server;
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
@ -49,10 +51,26 @@ public class ArclightServer {
|
||||||
return Objects.requireNonNull(server);
|
return Objects.requireNonNull(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isPrimaryThread() {
|
||||||
|
if (server == null) {
|
||||||
|
return Thread.currentThread().equals(getMinecraftServer().getExecutionThread());
|
||||||
|
} else {
|
||||||
|
return server.isPrimaryThread();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static MinecraftServer getMinecraftServer() {
|
public static MinecraftServer getMinecraftServer() {
|
||||||
return ServerLifecycleHooks.getCurrentServer();
|
return ServerLifecycleHooks.getCurrentServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void executeOnMainThread(Runnable runnable) {
|
||||||
|
((MinecraftServerBridge) getMinecraftServer()).bridge$queuedProcess(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Executor getMainThreadExecutor() {
|
||||||
|
return mainThreadExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
public static World.Environment getEnvironment(RegistryKey<DimensionType> key) {
|
public static World.Environment getEnvironment(RegistryKey<DimensionType> key) {
|
||||||
return BukkitRegistry.DIM_MAP.get(key);
|
return BukkitRegistry.DIM_MAP.get(key);
|
||||||
}
|
}
|
||||||
|
|
22
arclight-common/src/main/resources/async_catcher.json
Normal file
22
arclight-common/src/main/resources/async_catcher.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"net/minecraft/world/server/ServerWorld": {
|
||||||
|
"func_72838_d(Lnet/minecraft/entity/Entity;)Z": "entity add",
|
||||||
|
"func_217465_m(Lnet/minecraft/entity/Entity;)V": "entity register",
|
||||||
|
"removeEntityComplete(Lnet/minecraft/entity/Entity;Z)V": "entity unregister"
|
||||||
|
},
|
||||||
|
"net/minecraft/world/server/ChunkManager$EntityTracker": {
|
||||||
|
"func_219399_a(Lnet/minecraft/entity/player/ServerPlayerEntity;)V": "player tracker clear",
|
||||||
|
"func_219400_b(Lnet/minecraft/entity/player/ServerPlayerEntity;)V": "player tracker update"
|
||||||
|
},
|
||||||
|
"net/minecraft/block/Block": {
|
||||||
|
"func_220082_b(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Z)V": "block place",
|
||||||
|
"func_196243_a(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Z)V": "block remove"
|
||||||
|
},
|
||||||
|
"net/minecraft/entity/LivingEntity": {
|
||||||
|
"func_195064_c(Lnet/minecraft/potion/EffectInstance;)Z": "effect add"
|
||||||
|
},
|
||||||
|
"net/minecraft/world/server/ChunkManager": {
|
||||||
|
"func_219210_a(Lnet/minecraft/entity/Entity;)V": "entity track",
|
||||||
|
"func_219231_b(Lnet/minecraft/entity/Entity;)V": "entity untrack"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package io.izzel.arclight.i18n.conf;
|
||||||
|
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
public class AsyncCatcherSpec {
|
||||||
|
|
||||||
|
@Setting("dump")
|
||||||
|
private boolean dump;
|
||||||
|
|
||||||
|
@Setting("warn")
|
||||||
|
private boolean warn;
|
||||||
|
|
||||||
|
@Setting("defaultOperation")
|
||||||
|
private Operation defaultOp;
|
||||||
|
|
||||||
|
@Setting("overrides")
|
||||||
|
private Map<String, Operation> overrides;
|
||||||
|
|
||||||
|
public boolean isDump() {
|
||||||
|
return dump;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWarn() {
|
||||||
|
return warn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Operation getDefaultOp() {
|
||||||
|
return defaultOp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Operation> getOverrides() {
|
||||||
|
return overrides;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Operation {
|
||||||
|
NONE, DISPATCH, BLOCK, EXCEPTION
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,9 @@ public class ConfigSpec {
|
||||||
@Setting("compatibility")
|
@Setting("compatibility")
|
||||||
private CompatSpec compatSpec;
|
private CompatSpec compatSpec;
|
||||||
|
|
||||||
|
@Setting("async-catcher")
|
||||||
|
private AsyncCatcherSpec asyncCatcherSpec;
|
||||||
|
|
||||||
public int getVersion() {
|
public int getVersion() {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
@ -33,4 +36,8 @@ public class ConfigSpec {
|
||||||
public CompatSpec getCompat() {
|
public CompatSpec getCompat() {
|
||||||
return compatSpec;
|
return compatSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AsyncCatcherSpec getAsyncCatcher() {
|
||||||
|
return asyncCatcherSpec;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,4 +11,11 @@ compatibility {
|
||||||
}
|
}
|
||||||
entity-property-overrides {
|
entity-property-overrides {
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
async-catcher {
|
||||||
|
dump = true
|
||||||
|
warn = true
|
||||||
|
defaultOperation = block
|
||||||
|
overrides {
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -81,4 +81,13 @@ comments {
|
||||||
]
|
]
|
||||||
locale.comment = "语言/国际化相关设置"
|
locale.comment = "语言/国际化相关设置"
|
||||||
optimization.comment = "服务端优化相关设置"
|
optimization.comment = "服务端优化相关设置"
|
||||||
|
async-catcher.comment = [
|
||||||
|
"异步捕获相关设置"
|
||||||
|
"Async Catcher 共有四种模式"
|
||||||
|
"NONE - 保持在当前线程执行"
|
||||||
|
"DISPATCH - 不阻塞地发布到主线程"
|
||||||
|
"BLOCK - 发不到主线程并等待结果"
|
||||||
|
"EXCEPTION - 抛出异常"
|
||||||
|
]
|
||||||
|
async-catcher.dump.comment = "是否在 debug 日志中打印堆栈信息"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user