From c30575ec44ecbe5420886d7fa3a98d3db15a920e Mon Sep 17 00:00:00 2001 From: Seppe Volkaerts Date: Mon, 17 May 2021 21:01:17 +0200 Subject: [PATCH] Simplify EventTask. Add custom handler support. Additionally support for continuation adapters (will be used by the kotlin language plugin). --- .../ApiAnnotationProcessor.java | 8 - .../velocitypowered/api/event/EventTask.java | 112 +++--------- proxy/build.gradle | 2 +- .../proxy/event/CustomHandlerAdapter.java | 78 +++++++++ .../proxy/event/UntargetedEventHandler.java | 40 ++++- .../proxy/event/VelocityEventManager.java | 159 ++++++++++------- .../proxy/plugin/PluginClassLoader.java | 6 +- .../proxy/event/EventTest.java | 164 +++++++++++++----- 8 files changed, 355 insertions(+), 214 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/event/CustomHandlerAdapter.java diff --git a/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ApiAnnotationProcessor.java b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ApiAnnotationProcessor.java index 52975056d..2021543b0 100644 --- a/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ApiAnnotationProcessor.java +++ b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ApiAnnotationProcessor.java @@ -17,7 +17,6 @@ package com.velocitypowered.annotationprocessor; -import static com.velocitypowered.annotationprocessor.AnnotationProcessorConstants.EVENTTASK_CLASS; import static com.velocitypowered.annotationprocessor.AnnotationProcessorConstants.EVENT_INTERFACE; import static com.velocitypowered.annotationprocessor.AnnotationProcessorConstants.PLUGIN_ANNOTATION_CLASS; import static com.velocitypowered.annotationprocessor.AnnotationProcessorConstants.SUBSCRIBE_ANNOTATION_CLASS; @@ -30,9 +29,7 @@ import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.Set; -import javax.annotation.Nullable; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; import javax.annotation.processing.RoundEnvironment; @@ -45,7 +42,6 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; @@ -91,10 +87,6 @@ public class ApiAnnotationProcessor extends AbstractProcessor { msg.printMessage(Diagnostic.Kind.ERROR, "interfaces cannot declare listeners", method); } - if (method.getReturnType().getKind() != TypeKind.VOID - && !this.isTypeSubclass(method.getReturnType(), EVENTTASK_CLASS)) { - msg.printMessage(Kind.ERROR, "method must return void or EventTask", method); - } final List parameters = method.getParameters(); if (parameters.isEmpty() || !this.isTypeSubclass(parameters.get(0), EVENT_INTERFACE)) { diff --git a/api/src/main/java/com/velocitypowered/api/event/EventTask.java b/api/src/main/java/com/velocitypowered/api/event/EventTask.java index 3f72820c4..7471ba06f 100644 --- a/api/src/main/java/com/velocitypowered/api/event/EventTask.java +++ b/api/src/main/java/com/velocitypowered/api/event/EventTask.java @@ -17,13 +17,10 @@ import java.util.function.Consumer; * be suspended and resumed at a later time, and executing event handlers completely or partially * asynchronously. * - *

By default will all event handlers be executed on the thread the event was posted, using - * event tasks this behavior can be altered.

+ *

By default will all event handlers be executed on the thread the event was posted, this + * behavior can be altered by using event tasks.

*/ -public abstract class EventTask { - - EventTask() { - } +public interface EventTask { /** * Whether this {@link EventTask} is required to be called asynchronously. @@ -34,65 +31,22 @@ public abstract class EventTask { * * @return Requires async */ - public abstract boolean requiresAsync(); + boolean requiresAsync(); /** - * Represents a basic {@link EventTask}. The execution of the event handlers will resume after - * this basic task is executed using {@link #run()}. - */ - public abstract static class Basic extends EventTask { - - /** - * Runs the task. - */ - public abstract void run(); - } - - /** - * Represents an {@link EventTask} which receives a {@link Continuation} through - * {@link #run(Continuation)}. The continuation must be notified when the task is - * completed, either with {@link Continuation#resume()} if the task was successful or - * {@link Continuation#resumeWithException(Throwable)} if an exception occurred. + * Runs this event task with the given {@link Continuation}. The continuation must be notified + * when the task is completed, either with {@link Continuation#resume()} if the task was + * successful or {@link Continuation#resumeWithException(Throwable)} if an exception occurred. * *

The {@link Continuation} may only be resumed once, or an - * {@link IllegalStateException} is expected.

+ * {@link IllegalStateException} will be thrown.

* - *

The {@link Continuation} doesn't need to be notified during the execution of - * {@link #run(Continuation)}, this can happen at a later point in time and from another - * thread.

- */ - public abstract static class WithContinuation extends EventTask { - - /** - * Runs this async task with the given continuation. - * - * @param continuation The continuation - */ - public abstract void run(Continuation continuation); - } - - /** - * Creates a basic {@link EventTask} from the given {@link Runnable}. The task isn't guaranteed - * to be executed asynchronously ({@link #requiresAsync()} always returns {@code false}). + *

The {@link Continuation} doesn't need to be notified during the execution of this method, + * this can happen at a later point in time and from another thread.

* - * @param task The task - * @return The event task + * @param continuation The continuation */ - public static EventTask.Basic of(final Runnable task) { - requireNonNull(task, "task"); - return new Basic() { - - @Override - public void run() { - task.run(); - } - - @Override - public boolean requiresAsync() { - return false; - } - }; - } + void execute(Continuation continuation); /** * Creates a basic async {@link EventTask} from the given {@link Runnable}. The task is guaranteed @@ -101,13 +55,14 @@ public abstract class EventTask { * @param task The task * @return The async event task */ - public static EventTask.Basic async(final Runnable task) { + static EventTask async(final Runnable task) { requireNonNull(task, "task"); - return new Basic() { + return new EventTask() { @Override - public void run() { + public void execute(Continuation continuation) { task.run(); + continuation.resume(); } @Override @@ -125,13 +80,12 @@ public abstract class EventTask { * @param task The task to execute * @return The event task */ - public static EventTask.WithContinuation withContinuation( - final Consumer task) { + static EventTask withContinuation(final Consumer task) { requireNonNull(task, "task"); - return new WithContinuation() { + return new EventTask() { @Override - public void run(final Continuation continuation) { + public void execute(final Continuation continuation) { task.accept(continuation); } @@ -142,31 +96,6 @@ public abstract class EventTask { }; } - /** - * Creates an async continuation based {@link EventTask} from the given {@link Consumer}. The task - * is guaranteed to be executed asynchronously ({@link #requiresAsync()} always returns - * {@code false}). - * - * @param task The task to execute - * @return The event task - */ - public static EventTask.WithContinuation asyncWithContinuation( - final Consumer task) { - requireNonNull(task, "task"); - return new WithContinuation() { - - @Override - public void run(final Continuation continuation) { - task.accept(continuation); - } - - @Override - public boolean requiresAsync() { - return true; - } - }; - } - /** * Creates an continuation based {@link EventTask} for the given {@link CompletableFuture}. The * continuation will be notified once the given future is completed. @@ -177,8 +106,7 @@ public abstract class EventTask { // The Error Prone annotation here is spurious. The Future is handled via the CompletableFuture // API, which does NOT use the traditional blocking model. @SuppressWarnings("FutureReturnValueIgnored") - public static EventTask.WithContinuation resumeWhenComplete( - final CompletableFuture future) { + static EventTask resumeWhenComplete(final CompletableFuture future) { requireNonNull(future, "future"); return withContinuation(continuation -> future.whenComplete((result, cause) -> { if (cause != null) { diff --git a/proxy/build.gradle b/proxy/build.gradle index 089379d54..879eb6815 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -87,7 +87,7 @@ dependencies { implementation 'com.electronwill.night-config:toml:3.6.3' implementation 'org.bstats:bstats-base:2.2.0' - implementation 'org.lanternpowered:lmbda:2.0.0-SNAPSHOT' + implementation 'org.lanternpowered:lmbda:2.0.0' implementation 'com.github.ben-manes.caffeine:caffeine:2.8.8' implementation 'com.vdurmont:semver4j:3.1.0' diff --git a/proxy/src/main/java/com/velocitypowered/proxy/event/CustomHandlerAdapter.java b/proxy/src/main/java/com/velocitypowered/proxy/event/CustomHandlerAdapter.java new file mode 100644 index 000000000..1bf86efee --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/event/CustomHandlerAdapter.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.event; + +import com.google.common.reflect.TypeToken; +import com.velocitypowered.api.event.Event; +import com.velocitypowered.api.event.EventHandler; +import com.velocitypowered.api.event.EventTask; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.lanternpowered.lmbda.LambdaFactory; +import org.lanternpowered.lmbda.LambdaType; + +final class CustomHandlerAdapter { + + final String name; + private final Function> handlerBuilder; + final Predicate filter; + final BiConsumer> validator; + private final LambdaType functionType; + private final MethodHandles.Lookup methodHandlesLookup; + + @SuppressWarnings("unchecked") + CustomHandlerAdapter( + final String name, + final Predicate filter, + final BiConsumer> validator, + final TypeToken invokeFunctionType, + final Function> handlerBuilder, + final MethodHandles.Lookup methodHandlesLookup) { + this.name = name; + this.filter = filter; + this.validator = validator; + this.functionType = (LambdaType) LambdaType.of(invokeFunctionType.getRawType()); + this.handlerBuilder = handlerBuilder; + this.methodHandlesLookup = methodHandlesLookup; + } + + UntargetedEventHandler buildUntargetedHandler(final Method method) + throws IllegalAccessException { + final MethodHandle methodHandle = methodHandlesLookup.unreflect(method); + final MethodHandles.Lookup defineLookup = MethodHandles.privateLookupIn( + method.getDeclaringClass(), methodHandlesLookup); + final LambdaType lambdaType = functionType.defineClassesWith(defineLookup); + final F invokeFunction = LambdaFactory.create(lambdaType, methodHandle); + final BiFunction handlerFunction = + handlerBuilder.apply(invokeFunction); + return targetInstance -> new EventHandler<>() { + + @Override + public @Nullable EventTask execute(Event event) { + return handlerFunction.apply(targetInstance, event); + } + }; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/event/UntargetedEventHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/event/UntargetedEventHandler.java index aa6f0ff7e..7113167a0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/event/UntargetedEventHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/event/UntargetedEventHandler.java @@ -17,21 +17,47 @@ package com.velocitypowered.proxy.event; +import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.event.Event; +import com.velocitypowered.api.event.EventHandler; import com.velocitypowered.api.event.EventTask; import org.checkerframework.checker.nullness.qual.Nullable; public interface UntargetedEventHandler { - @Nullable EventTask execute(Object targetInstance, Object event); + EventHandler buildHandler(Object targetInstance); + + interface EventTaskHandler extends UntargetedEventHandler { + + @Nullable EventTask execute(Object targetInstance, Event event); + + @Override + default EventHandler buildHandler(final Object targetInstance) { + return event -> execute(targetInstance, event); + } + } interface VoidHandler extends UntargetedEventHandler { - @Override - default @Nullable EventTask execute(final Object targetInstance, final Object event) { - executeVoid(targetInstance, event); - return null; - } + void execute(Object targetInstance, Object event); - void executeVoid(Object targetInstance, Object event); + @Override + default EventHandler buildHandler(final Object targetInstance) { + return event -> { + execute(targetInstance, event); + return null; + }; + } + } + + interface WithContinuationHandler extends UntargetedEventHandler { + + void execute(Object targetInstance, Object event, Continuation continuation); + + @Override + default EventHandler buildHandler(final Object targetInstance) { + return event -> EventTask.withContinuation(continuation -> + execute(targetInstance, event, continuation)); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java b/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java index 5b738a5ca..1562c03e3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java @@ -34,7 +34,10 @@ import com.velocitypowered.api.event.EventTask; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginManager; +import com.velocitypowered.proxy.event.UntargetedEventHandler.EventTaskHandler; import com.velocitypowered.proxy.event.UntargetedEventHandler.VoidHandler; +import com.velocitypowered.proxy.event.UntargetedEventHandler.WithContinuationHandler; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.reflect.Method; @@ -55,6 +58,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; @@ -68,10 +74,12 @@ public class VelocityEventManager implements EventManager { private static final Logger logger = LogManager.getLogger(VelocityEventManager.class); private static final MethodHandles.Lookup methodHandlesLookup = MethodHandles.lookup(); - private static final LambdaType untargetedHandlerType = - LambdaType.of(UntargetedEventHandler.class); + private static final LambdaType untargetedEventTaskHandlerType = + LambdaType.of(EventTaskHandler.class); private static final LambdaType untargetedVoidHandlerType = LambdaType.of(VoidHandler.class); + private static final LambdaType untargetedWithContinuationHandlerType = + LambdaType.of(WithContinuationHandler.class); private static final Comparator handlerComparator = Comparator.comparingInt(o -> o.order); @@ -88,6 +96,8 @@ public class VelocityEventManager implements EventManager { private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final List> handlerAdapters = new ArrayList<>(); + /** * Initializes the Velocity event manager. * @@ -100,6 +110,20 @@ public class VelocityEventManager implements EventManager { .setNameFormat("Velocity Async Event Executor - #%d").setDaemon(true).build()); } + /** + * Registers a new continuation adapter function. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public void registerHandlerAdapter( + final String name, + final Predicate filter, + final BiConsumer> validator, + final TypeToken invokeFunctionType, + final Function> handlerBuilder) { + handlerAdapters.add(new CustomHandlerAdapter(name, filter, validator, + invokeFunctionType, handlerBuilder, methodHandlesLookup)); + } + /** * Represents the registration of a single {@link EventHandler}. */ @@ -112,8 +136,7 @@ public class VelocityEventManager implements EventManager { final AsyncType asyncType; /** - * The instance of the {@link EventHandler} or the listener - * instance that was registered. + * The instance of the {@link EventHandler} or the listener instance that was registered. */ final Object instance; @@ -206,15 +229,23 @@ public class VelocityEventManager implements EventManager { */ private UntargetedEventHandler buildUntargetedMethodHandler(final Method method) throws IllegalAccessException { + for (final CustomHandlerAdapter handlerAdapter : handlerAdapters) { + if (handlerAdapter.filter.test(method)) { + return handlerAdapter.buildUntargetedHandler(method); + } + } final MethodHandles.Lookup lookup = MethodHandles.privateLookupIn( method.getDeclaringClass(), methodHandlesLookup); + final MethodHandle methodHandle = lookup.unreflect(method); final LambdaType type; if (EventTask.class.isAssignableFrom(method.getReturnType())) { - type = untargetedHandlerType; + type = untargetedEventTaskHandlerType; + } else if (method.getParameterCount() == 2) { + type = untargetedWithContinuationHandlerType; } else { type = untargetedVoidHandlerType; } - return LambdaFactory.create(type.defineClassesWith(lookup), lookup.unreflect(method)); + return LambdaFactory.create(type.defineClassesWith(lookup), methodHandle); } static final class MethodHandlerInfo { @@ -224,14 +255,17 @@ public class VelocityEventManager implements EventManager { final @Nullable Class eventType; final short order; final @Nullable String errors; + final @Nullable Class continuationType; private MethodHandlerInfo(final Method method, final AsyncType asyncType, - final @Nullable Class eventType, final short order, final @Nullable String errors) { + final @Nullable Class eventType, final short order, final @Nullable String errors, + final @Nullable Class continuationType) { this.method = method; this.asyncType = asyncType; this.eventType = eventType; this.order = order; this.errors = errors; + this.continuationType = continuationType; } } @@ -262,22 +296,51 @@ public class VelocityEventManager implements EventManager { errors.add("method must not be abstract"); } Class eventType = null; - if (method.getParameterCount() != 1) { - errors.add("method must have a single parameter which is the event"); + Class continuationType = null; + CustomHandlerAdapter handlerAdapter = null; + final int paramCount = method.getParameterCount(); + if (paramCount == 0) { + errors.add("method must have at least one parameter which is the event"); } else { - eventType = method.getParameterTypes()[0]; + final Class[] parameterTypes = method.getParameterTypes(); + eventType = parameterTypes[0]; + if (!Event.class.isAssignableFrom(eventType)) { + errors.add(String.format("first method parameter must be the event, %s is invalid", + eventType.getName())); + } + for (final CustomHandlerAdapter handlerAdapterCandidate : handlerAdapters) { + if (handlerAdapterCandidate.filter.test(method)) { + handlerAdapter = handlerAdapterCandidate; + break; + } + } + if (handlerAdapter != null) { + final List adapterErrors = new ArrayList<>(); + handlerAdapter.validator.accept(method, adapterErrors); + if (!adapterErrors.isEmpty()) { + errors.add(String.format("%s adapter errors: [%s]", + handlerAdapter.name, String.join(", ", adapterErrors))); + } + } else if (paramCount == 2) { + continuationType = parameterTypes[1]; + if (continuationType != Continuation.class) { + errors.add(String.format("method is allowed to have a continuation as second parameter," + + " but %s is invalid", continuationType.getName())); + } + } } AsyncType asyncType = AsyncType.NEVER; - final Class returnType = method.getReturnType(); - if (returnType != void.class - && returnType != EventTask.class - && returnType != EventTask.Basic.class - && returnType != EventTask.WithContinuation.class) { - errors.add("method return type must be void, AsyncTask, " - + "AsyncTask.Basic or AsyncTask.WithContinuation"); - } else if (returnType == EventTask.class - || returnType == EventTask.Basic.class - || returnType == EventTask.WithContinuation.class) { + if (handlerAdapter == null) { + final Class returnType = method.getReturnType(); + if (returnType != void.class && continuationType == Continuation.class) { + errors.add("method return type must be void if a continuation parameter is provided"); + } else if (returnType != void.class && returnType != EventTask.class) { + errors.add("method return type must be void, AsyncTask, " + + "AsyncTask.Basic or AsyncTask.WithContinuation"); + } else if (returnType == EventTask.class) { + asyncType = AsyncType.SOMETIMES; + } + } else { asyncType = AsyncType.SOMETIMES; } if (subscribe.async()) { @@ -285,7 +348,7 @@ public class VelocityEventManager implements EventManager { } final short order = subscribe.order(); final String errorsJoined = errors.isEmpty() ? null : String.join(",", errors); - collected.put(key, new MethodHandlerInfo(method, asyncType, eventType, order, errorsJoined)); + collected.put(key, new MethodHandlerInfo(method, asyncType, eventType, order, errorsJoined, continuationType)); } final Class superclass = targetClass.getSuperclass(); if (superclass != Object.class) { @@ -322,7 +385,7 @@ public class VelocityEventManager implements EventManager { @Override @SuppressWarnings("unchecked") public void register(final Object plugin, final Class eventClass, - final short order, final EventHandler handler) { + final short order, final EventHandler handler) { final PluginContainer pluginContainer = pluginManager.ensurePluginContainer(plugin); requireNonNull(eventClass, "eventClass"); requireNonNull(handler, "handler"); @@ -350,7 +413,7 @@ public class VelocityEventManager implements EventManager { throw new VerifyException("Event type is not present and there are no errors"); } - final EventHandler handler = event -> untargetedHandler.execute(listener, event); + final EventHandler handler = untargetedHandler.buildHandler(listener); registrations.add(new HandlerRegistration(pluginContainer, info.order, info.eventType, listener, handler, info.asyncType)); } @@ -455,7 +518,7 @@ public class VelocityEventManager implements EventManager { final class ContinuationTask implements Continuation, Runnable { - private final EventTask.WithContinuation task; + private final EventTask task; private final int index; private final HandlerRegistration[] registrations; private final @Nullable CompletableFuture future; @@ -463,7 +526,7 @@ public class VelocityEventManager implements EventManager { private final E event; // This field is modified via a VarHandle, so this field is used and cannot be final. - @SuppressWarnings({"UnusedVariable", "FieldMayBeFinal"}) + @SuppressWarnings({"UnusedVariable", "FieldMayBeFinal", "FieldCanBeLocal"}) private volatile int state = TASK_STATE_DEFAULT; // This field is modified via a VarHandle, so this field is used and cannot be final. @@ -471,7 +534,7 @@ public class VelocityEventManager implements EventManager { private volatile boolean resumed = false; private ContinuationTask( - final EventTask.WithContinuation task, + final EventTask task, final HandlerRegistration[] registrations, final @Nullable CompletableFuture future, final E event, @@ -499,7 +562,7 @@ public class VelocityEventManager implements EventManager { boolean execute() { state = TASK_STATE_EXECUTING; try { - task.run(this); + task.execute(this); } catch (final Throwable t) { // validateOnlyOnce false here so don't get an exception if the // continuation was resumed before @@ -555,42 +618,18 @@ public class VelocityEventManager implements EventManager { if (eventTask == null) { continue; } - if (eventTask instanceof EventTask.WithContinuation) { - final EventTask.WithContinuation withContinuation = - (EventTask.WithContinuation) eventTask; - final ContinuationTask continuationTask = new ContinuationTask<>(withContinuation, - registrations, future, event, i, currentlyAsync); - if (currentlyAsync || !eventTask.requiresAsync()) { - if (continuationTask.execute()) { - continue; - } - } else { - asyncExecutor.execute(continuationTask); + final ContinuationTask continuationTask = new ContinuationTask<>(eventTask, + registrations, future, event, i, currentlyAsync); + if (currentlyAsync || !eventTask.requiresAsync()) { + if (continuationTask.execute()) { + continue; } - // fire will continue in another thread once the async task is - // executed and the continuation is resumed - return; } else { - final EventTask.Basic basic = (EventTask.Basic) eventTask; - if (currentlyAsync || !basic.requiresAsync()) { - // We are already async or we don't need async, so we can just run the - // task and continue with the next handler - basic.run(); - } else { - final int index = i; - // We are not yet in an async context, so the async task needs to be scheduled - // to the async executor, the event handling will continue on an async thread. - asyncExecutor.execute(() -> { - try { - basic.run(); - } catch (final Throwable t) { - logHandlerException(registration, t); - } - fire(future, event, index + 1, true, registrations); - }); - return; // fire will continue in another thread once the async task is completed - } + asyncExecutor.execute(continuationTask); } + // fire will continue in another thread once the async task is + // executed and the continuation is resumed + return; } catch (final Throwable t) { logHandlerException(registration, t); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java index b5928502a..3c95a7b24 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java @@ -64,9 +64,13 @@ public class PluginClassLoader extends URLClassLoader { return findClass0(name, true); } + private boolean isKtLanguagePlugin() { + return description.id().equals("velocity-language-kotlin"); + } + private Class findClass0(String name, boolean checkOther) throws ClassNotFoundException { - if (name.startsWith("com.velocitypowered")) { + if (name.startsWith("com.velocitypowered") && !isKtLanguagePlugin()) { throw new ClassNotFoundException(); } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/event/EventTest.java b/proxy/src/test/java/com/velocitypowered/proxy/event/EventTest.java index 8ec6f0f77..e3cb7119a 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/event/EventTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/event/EventTest.java @@ -20,6 +20,8 @@ package com.velocitypowered.proxy.event; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.google.common.reflect.TypeToken; +import com.velocitypowered.api.event.Continuation; import com.velocitypowered.api.event.Event; import com.velocitypowered.api.event.EventTask; import com.velocitypowered.api.event.PostOrder; @@ -100,19 +102,19 @@ public class EventTest { int result; @Subscribe(async = true) - void async0(TestEvent event) { + void firstAsync(TestEvent event) { result++; threadA = Thread.currentThread(); } @Subscribe - EventTask async1(TestEvent event) { + EventTask secondAsync(TestEvent event) { threadB = Thread.currentThread(); return EventTask.async(() -> result++); } @Subscribe - void async2(TestEvent event) { + void thirdAsync(TestEvent event) { result++; threadC = Thread.currentThread(); } @@ -201,48 +203,6 @@ public class EventTest { } } - @Test - void testAsyncContinuation() throws Exception { - final AsyncContinuationListener listener = new AsyncContinuationListener(); - handleMethodListener(listener); - assertSyncThread(listener.threadA); - assertAsyncThread(listener.threadB); - assertAsyncThread(listener.threadC); - assertEquals(2, listener.value.get()); - } - - static final class AsyncContinuationListener { - - @MonotonicNonNull Thread threadA; - @MonotonicNonNull Thread threadB; - @MonotonicNonNull Thread threadC; - - final AtomicInteger value = new AtomicInteger(); - - @Subscribe(order = PostOrder.EARLY) - EventTask continuation(TestEvent event) { - threadA = Thread.currentThread(); - return EventTask.asyncWithContinuation(continuation -> { - value.incrementAndGet(); - threadB = Thread.currentThread(); - new Thread(() -> { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - value.incrementAndGet(); - continuation.resume(); - }).start(); - }); - } - - @Subscribe(order = PostOrder.LATE) - void afterContinuation(TestEvent event) { - threadC = Thread.currentThread(); - } - } - @Test void testResumeContinuationImmediately() throws Exception { final ResumeContinuationImmediatelyListener listener = new ResumeContinuationImmediatelyListener(); @@ -276,4 +236,118 @@ public class EventTest { result++; } } + + @Test + void testContinuationParameter() throws Exception { + final ContinuationParameterListener listener = new ContinuationParameterListener(); + handleMethodListener(listener); + assertSyncThread(listener.threadA); + assertSyncThread(listener.threadB); + assertAsyncThread(listener.threadC); + assertEquals(3, listener.result.get()); + } + + static final class ContinuationParameterListener { + + @MonotonicNonNull Thread threadA; + @MonotonicNonNull Thread threadB; + @MonotonicNonNull Thread threadC; + + final AtomicInteger result = new AtomicInteger(); + + @Subscribe + void resume(TestEvent event, Continuation continuation) { + threadA = Thread.currentThread(); + result.incrementAndGet(); + continuation.resume(); + } + + @Subscribe(order = PostOrder.LATE) + void resumeFromCustomThread(TestEvent event, Continuation continuation) { + threadB = Thread.currentThread(); + new Thread(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + result.incrementAndGet(); + continuation.resume(); + }).start(); + } + + @Subscribe(order = PostOrder.LAST) + void afterCustomThread(TestEvent event, Continuation continuation) { + threadC = Thread.currentThread(); + result.incrementAndGet(); + continuation.resume(); + } + } + + interface FancyContinuation { + + void resume(); + + void resumeWithError(Exception exception); + } + + private static final class FancyContinuationImpl implements FancyContinuation { + + private final Continuation continuation; + + private FancyContinuationImpl(final Continuation continuation) { + this.continuation = continuation; + } + + @Override + public void resume() { + continuation.resume(); + } + + @Override + public void resumeWithError(final Exception exception) { + continuation.resumeWithException(exception); + } + } + + interface TriConsumer { + + void accept(A a, B b, C c); + } + + @Test + void testFancyContinuationParameter() throws Exception { + eventManager.registerHandlerAdapter( + "fancy", + method -> method.getParameterCount() > 1 + && method.getParameterTypes()[1] == FancyContinuation.class, + (method, errors) -> { + if (method.getReturnType() != void.class) { + errors.add("method return type must be void"); + } + if (method.getParameterCount() != 2) { + errors.add("method must have exactly two parameters, the first is the event and " + + "the second is the fancy continuation"); + } + }, + new TypeToken>() {}, + invokeFunction -> (instance, event) -> + EventTask.withContinuation(continuation -> + invokeFunction.accept(instance, event, new FancyContinuationImpl(continuation)) + )); + final FancyContinuationListener listener = new FancyContinuationListener(); + handleMethodListener(listener); + assertEquals(1, listener.result); + } + + static final class FancyContinuationListener { + + int result; + + @Subscribe + void continuation(TestEvent event, FancyContinuation continuation) { + result++; + continuation.resume(); + } + } }