Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-16 21:10:30 +01:00
Merge pull request #504 from Cybermaxke/event-task-improvements
Simplify EventTask. Add Continuation parameter support.
Dieser Commit ist enthalten in:
Commit
1f0a186766
@ -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<? extends VariableElement> parameters = method.getParameters();
|
||||
if (parameters.isEmpty()
|
||||
|| !this.isTypeSubclass(parameters.get(0), EVENT_INTERFACE)) {
|
||||
|
@ -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.
|
||||
*
|
||||
* <p>By default will all event handlers be executed on the thread the event was posted, using
|
||||
* event tasks this behavior can be altered.</p>
|
||||
* <p>By default will all event handlers be executed on the thread the event was posted, this
|
||||
* behavior can be altered by using event tasks.</p>
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* <p>The {@link Continuation} may only be resumed once, or an
|
||||
* {@link IllegalStateException} is expected.</p>
|
||||
* {@link IllegalStateException} will be thrown.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*/
|
||||
public abstract static class WithContinuation extends EventTask {
|
||||
|
||||
/**
|
||||
* Runs this async task with the given continuation.
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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}).
|
||||
*
|
||||
* @param task The task
|
||||
* @return The event task
|
||||
*/
|
||||
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<Continuation> task) {
|
||||
static EventTask withContinuation(final Consumer<Continuation> 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<Continuation> 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) {
|
||||
|
@ -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'
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<F> {
|
||||
|
||||
final String name;
|
||||
private final Function<F, BiFunction<Object, Event, EventTask>> handlerBuilder;
|
||||
final Predicate<Method> filter;
|
||||
final BiConsumer<Method, List<String>> validator;
|
||||
private final LambdaType<F> functionType;
|
||||
private final MethodHandles.Lookup methodHandlesLookup;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
CustomHandlerAdapter(
|
||||
final String name,
|
||||
final Predicate<Method> filter,
|
||||
final BiConsumer<Method, List<String>> validator,
|
||||
final TypeToken<F> invokeFunctionType,
|
||||
final Function<F, BiFunction<Object, Event, EventTask>> handlerBuilder,
|
||||
final MethodHandles.Lookup methodHandlesLookup) {
|
||||
this.name = name;
|
||||
this.filter = filter;
|
||||
this.validator = validator;
|
||||
this.functionType = (LambdaType<F>) 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<F> lambdaType = functionType.defineClassesWith(defineLookup);
|
||||
final F invokeFunction = LambdaFactory.create(lambdaType, methodHandle);
|
||||
final BiFunction<Object, Event, EventTask> handlerFunction =
|
||||
handlerBuilder.apply(invokeFunction);
|
||||
return targetInstance -> new EventHandler<>() {
|
||||
|
||||
@Override
|
||||
public @Nullable EventTask execute(Event event) {
|
||||
return handlerFunction.apply(targetInstance, event);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -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<Event> buildHandler(Object targetInstance);
|
||||
|
||||
interface EventTaskHandler extends UntargetedEventHandler {
|
||||
|
||||
@Nullable EventTask execute(Object targetInstance, Event event);
|
||||
|
||||
@Override
|
||||
default EventHandler<Event> buildHandler(final Object targetInstance) {
|
||||
return event -> execute(targetInstance, event);
|
||||
}
|
||||
}
|
||||
|
||||
interface VoidHandler extends UntargetedEventHandler {
|
||||
|
||||
void execute(Object targetInstance, Object event);
|
||||
|
||||
@Override
|
||||
default @Nullable EventTask execute(final Object targetInstance, final Object event) {
|
||||
executeVoid(targetInstance, event);
|
||||
default EventHandler<Event> buildHandler(final Object targetInstance) {
|
||||
return event -> {
|
||||
execute(targetInstance, event);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void executeVoid(Object targetInstance, Object event);
|
||||
interface WithContinuationHandler extends UntargetedEventHandler {
|
||||
|
||||
void execute(Object targetInstance, Object event, Continuation continuation);
|
||||
|
||||
@Override
|
||||
default EventHandler<Event> buildHandler(final Object targetInstance) {
|
||||
return event -> EventTask.withContinuation(continuation ->
|
||||
execute(targetInstance, event, continuation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<UntargetedEventHandler> untargetedHandlerType =
|
||||
LambdaType.of(UntargetedEventHandler.class);
|
||||
private static final LambdaType<EventTaskHandler> untargetedEventTaskHandlerType =
|
||||
LambdaType.of(EventTaskHandler.class);
|
||||
private static final LambdaType<VoidHandler> untargetedVoidHandlerType =
|
||||
LambdaType.of(VoidHandler.class);
|
||||
private static final LambdaType<WithContinuationHandler> untargetedWithContinuationHandlerType =
|
||||
LambdaType.of(WithContinuationHandler.class);
|
||||
|
||||
private static final Comparator<HandlerRegistration> handlerComparator =
|
||||
Comparator.comparingInt(o -> o.order);
|
||||
@ -88,6 +96,8 @@ public class VelocityEventManager implements EventManager {
|
||||
|
||||
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
private final List<CustomHandlerAdapter<?>> 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 <F> void registerHandlerAdapter(
|
||||
final String name,
|
||||
final Predicate<Method> filter,
|
||||
final BiConsumer<Method, List<String>> validator,
|
||||
final TypeToken<F> invokeFunctionType,
|
||||
final Function<F, BiFunction<Object, Event, EventTask>> 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<? extends UntargetedEventHandler> 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<String> 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;
|
||||
if (handlerAdapter == null) {
|
||||
final Class<?> returnType = method.getReturnType();
|
||||
if (returnType != void.class
|
||||
&& returnType != EventTask.class
|
||||
&& returnType != EventTask.Basic.class
|
||||
&& returnType != EventTask.WithContinuation.class) {
|
||||
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
|
||||
|| returnType == EventTask.Basic.class
|
||||
|| returnType == EventTask.WithContinuation.class) {
|
||||
} 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) {
|
||||
@ -350,7 +413,7 @@ public class VelocityEventManager implements EventManager {
|
||||
throw new VerifyException("Event type is not present and there are no errors");
|
||||
}
|
||||
|
||||
final EventHandler<Event> handler = event -> untargetedHandler.execute(listener, event);
|
||||
final EventHandler<Event> 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<E extends Event> implements Continuation, Runnable {
|
||||
|
||||
private final EventTask.WithContinuation task;
|
||||
private final EventTask task;
|
||||
private final int index;
|
||||
private final HandlerRegistration[] registrations;
|
||||
private final @Nullable CompletableFuture<E> 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<E> 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,10 +618,7 @@ public class VelocityEventManager implements EventManager {
|
||||
if (eventTask == null) {
|
||||
continue;
|
||||
}
|
||||
if (eventTask instanceof EventTask.WithContinuation) {
|
||||
final EventTask.WithContinuation withContinuation =
|
||||
(EventTask.WithContinuation) eventTask;
|
||||
final ContinuationTask<E> continuationTask = new ContinuationTask<>(withContinuation,
|
||||
final ContinuationTask<E> continuationTask = new ContinuationTask<>(eventTask,
|
||||
registrations, future, event, i, currentlyAsync);
|
||||
if (currentlyAsync || !eventTask.requiresAsync()) {
|
||||
if (continuationTask.execute()) {
|
||||
@ -570,27 +630,6 @@ public class VelocityEventManager implements EventManager {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
} catch (final Throwable t) {
|
||||
logHandlerException(registration, t);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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<A, B, C> {
|
||||
|
||||
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<TriConsumer<Object, Event, FancyContinuation>>() {},
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren