Backport Velocity Polymer's async event API, with changes.

This commit backports the event manager from Velocity Polymer, with some changes for Velocity 1.1.x API compatibility:

- All event handlers run asynchronously. (While EventTask.async() exists, it is not useful in 3.0.0, but is provided as a migration aid for Polymer.)
- Event ordering is currently limited to the 5 levels available in Velocity 1.x.x.
This commit is contained in:
Seppe Volkaerts
2021-05-23 15:38:35 -04:00
committed by Andrew Steinborn
parent 3f50964f36
commit 821ca02ee7
16 changed files with 1292 additions and 257 deletions

View File

@@ -0,0 +1,21 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event;
import org.checkerframework.checker.nullness.qual.Nullable;
@FunctionalInterface
public interface AsyncEventExecutor<E> extends EventHandler<E> {
default void execute(E event) {
throw new UnsupportedOperationException(
"This event handler can only be invoked asynchronously.");
}
@Nullable EventTask executeAsync(E event);
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event;
/**
* Represents a continuation of a paused event handler. Any of the resume methods
* may only be called once otherwise an {@link IllegalStateException} is expected.
*/
public interface Continuation {
/**
* Resumes the continuation.
*/
void resume();
/**
* Resumes the continuation after the executed task failed.
*/
void resumeWithException(Throwable exception);
}

View File

@@ -7,12 +7,21 @@
package com.velocitypowered.api.event;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents an interface to perform direct dispatch of an event. This makes integration easier to
* achieve with platforms such as RxJava.
* achieve with platforms such as RxJava. While this interface can be used to implement an
* asynchronous event handler, {@link AsyncEventExecutor} provides a more idiomatic means of doing
* so.
*/
@FunctionalInterface
public interface EventHandler<E> {
void execute(E event);
default @Nullable EventTask executeAsync(E event) {
execute(event);
return null;
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event;
import static java.util.Objects.requireNonNull;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* Represents a task that can be returned by a {@link EventHandler} which allows event handling to
* be suspended and resumed at a later time, and executing event handlers completely or partially
* asynchronously.
*
* <p><strong>Compatibility notice:</strong> While in Velocity 3.0.0, all event handlers still
* execute asynchronously (to preserve backwards compatibility), this will not be the case in future
* versions of Velocity. Please prepare your code by using continuations or returning an instance
* returned by {@link #async(Runnable)}.</p>
*/
public interface EventTask {
/**
* Whether this {@link EventTask} is required to be called asynchronously.
*
* <p>If this method returns {@code true}, the event task is guaranteed to be executed
* asynchronously from the current thread. Otherwise, the event task may be executed on the
* current thread or asynchronously.</p>
*
* @return Requires async
*/
boolean requiresAsync();
/**
* 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} will be thrown.</p>
*
* <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
*/
void execute(Continuation continuation);
/**
* Creates a basic async {@link EventTask} from the given {@link Runnable}. The task is guaranteed
* to be executed asynchronously ({@link #requiresAsync()} always returns {@code true}).
*
* @param task The task
* @return The async event task
*/
static EventTask async(final Runnable task) {
requireNonNull(task, "task");
return new EventTask() {
@Override
public void execute(Continuation continuation) {
task.run();
continuation.resume();
}
@Override
public boolean requiresAsync() {
return true;
}
};
}
/**
* Creates an continuation based {@link EventTask} from the given {@link Consumer}. The task isn't
* guaranteed to be executed asynchronously ({@link #requiresAsync()} always returns
* {@code false}).
*
* @param task The task to execute
* @return The event task
*/
static EventTask withContinuation(final Consumer<Continuation> task) {
requireNonNull(task, "task");
return new EventTask() {
@Override
public void execute(final Continuation continuation) {
task.accept(continuation);
}
@Override
public boolean requiresAsync() {
return false;
}
};
}
/**
* Creates an continuation based {@link EventTask} for the given {@link CompletableFuture}. The
* continuation will be notified once the given future is completed.
*
* @param future The task to wait for
* @return The event task
*/
// 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")
static EventTask resumeWhenComplete(final CompletableFuture<?> future) {
requireNonNull(future, "future");
return withContinuation(continuation -> future.whenComplete((result, cause) -> {
if (cause != null) {
continuation.resumeWithException(cause);
} else {
continuation.resume();
}
}));
}
}

View File

@@ -57,4 +57,16 @@ public interface PluginManager {
* @throws UnsupportedOperationException if the operation is not applicable to this plugin
*/
void addToClasspath(Object plugin, Path path);
/**
* Ensures a plugin container exists for the given {@code plugin}.
*
* @param plugin the instance to look up the container for
* @return container for the plugin
*/
default PluginContainer ensurePluginContainer(Object plugin) {
return this.fromInstance(plugin)
.orElseThrow(() -> new IllegalArgumentException(plugin.getClass().getCanonicalName()
+ " does not have a container."));
}
}