Use hidden classes for event executors (#11848)
Static final MethodHandles perform similar to direct calls. Additionally, hidden classes simplify logic around ClassLoaders as they can be defined weakly coupled to their defining class loader. All variants of methods (static, private, non-void) can be covered by this mechanism.
This commit is contained in:
@@ -6,16 +6,10 @@ import org.bukkit.event.Listener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
// Paper start
|
||||
import io.papermc.paper.event.executor.EventExecutorFactory;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.destroystokyo.paper.event.executor.MethodHandleEventExecutor;
|
||||
import com.destroystokyo.paper.event.executor.StaticMethodHandleEventExecutor;
|
||||
import com.destroystokyo.paper.event.executor.asm.ASMEventExecutorGenerator;
|
||||
import com.destroystokyo.paper.event.executor.asm.ClassDefiner;
|
||||
import com.google.common.base.Preconditions;
|
||||
// Paper end
|
||||
|
||||
@@ -26,69 +20,25 @@ public interface EventExecutor {
|
||||
public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException;
|
||||
|
||||
// Paper start
|
||||
ConcurrentMap<Method, Class<? extends EventExecutor>> eventExecutorMap = new ConcurrentHashMap<Method, Class<? extends EventExecutor>>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public Class<? extends EventExecutor> computeIfAbsent(@NotNull Method key, @NotNull Function<? super Method, ? extends Class<? extends EventExecutor>> mappingFunction) {
|
||||
Class<? extends EventExecutor> executorClass = get(key);
|
||||
if (executorClass != null)
|
||||
return executorClass;
|
||||
|
||||
//noinspection SynchronizationOnLocalVariableOrMethodParameter
|
||||
synchronized (key) {
|
||||
executorClass = get(key);
|
||||
if (executorClass != null)
|
||||
return executorClass;
|
||||
|
||||
return super.computeIfAbsent(key, mappingFunction);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@NotNull
|
||||
public static EventExecutor create(@NotNull Method m, @NotNull Class<? extends Event> eventClass) {
|
||||
Preconditions.checkNotNull(m, "Null method");
|
||||
Preconditions.checkArgument(m.getParameterCount() != 0, "Incorrect number of arguments %s", m.getParameterCount());
|
||||
Preconditions.checkArgument(m.getParameterTypes()[0] == eventClass, "First parameter %s doesn't match event class %s", m.getParameterTypes()[0], eventClass);
|
||||
ClassDefiner definer = ClassDefiner.getInstance();
|
||||
if (m.getReturnType() != Void.TYPE) {
|
||||
final org.bukkit.plugin.java.JavaPlugin plugin = org.bukkit.plugin.java.JavaPlugin.getProvidingPlugin(m.getDeclaringClass());
|
||||
org.bukkit.Bukkit.getLogger().warning("@EventHandler method " + m.getDeclaringClass().getName() + (Modifier.isStatic(m.getModifiers()) ? '.' : '#') + m.getName()
|
||||
+ " returns non-void type " + m.getReturnType().getName() + ". This is unsupported behavior and will no longer work in a future version of Paper."
|
||||
+ " This should be reported to the developers of " + plugin.getPluginMeta().getDisplayName() + " (" + String.join(",", plugin.getPluginMeta().getAuthors()) + ')');
|
||||
}
|
||||
if (Modifier.isStatic(m.getModifiers())) {
|
||||
return new StaticMethodHandleEventExecutor(eventClass, m);
|
||||
} else if (definer.isBypassAccessChecks() || Modifier.isPublic(m.getDeclaringClass().getModifiers()) && Modifier.isPublic(m.getModifiers())) {
|
||||
// get the existing generated EventExecutor class for the Method or generate one
|
||||
Class<? extends EventExecutor> executorClass = eventExecutorMap.computeIfAbsent(m, (__) -> {
|
||||
String name = ASMEventExecutorGenerator.generateName();
|
||||
byte[] classData = ASMEventExecutorGenerator.generateEventExecutor(m, name);
|
||||
return definer.defineClass(m.getDeclaringClass().getClassLoader(), name, classData).asSubclass(EventExecutor.class);
|
||||
});
|
||||
|
||||
try {
|
||||
EventExecutor asmExecutor = executorClass.newInstance();
|
||||
// Define a wrapper to conform to bukkit stupidity (passing in events that don't match and wrapper exception)
|
||||
return new EventExecutor() {
|
||||
@Override
|
||||
public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException {
|
||||
if (!eventClass.isInstance(event)) return;
|
||||
asmExecutor.execute(listener, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toString() {
|
||||
return "ASMEventExecutor['" + m + "']";
|
||||
}
|
||||
};
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new AssertionError("Unable to initialize generated event executor", e);
|
||||
}
|
||||
} else {
|
||||
return new MethodHandleEventExecutor(eventClass, m);
|
||||
if (!m.trySetAccessible()) {
|
||||
final org.bukkit.plugin.java.JavaPlugin plugin = org.bukkit.plugin.java.JavaPlugin.getProvidingPlugin(m.getDeclaringClass());
|
||||
throw new AssertionError(
|
||||
"@EventHandler method " + m.getDeclaringClass().getName() + (Modifier.isStatic(m.getModifiers()) ? '.' : '#') + m.getName() + " is not accessible."
|
||||
+ " This should be reported to the developers of " + plugin.getDescription().getName() + " (" + String.join(",", plugin.getDescription().getAuthors()) + ')'
|
||||
);
|
||||
}
|
||||
return EventExecutorFactory.create(m, eventClass);
|
||||
}
|
||||
// Paper end
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user