Skip to content

Commit adb7cd4

Browse files
authored
✨ feat(event-handlers): add support for custom suspending event handlers in Velocity (#341)
2 parents c4ee9d0 + 536ec3f commit adb7cd4

5 files changed

Lines changed: 83 additions & 20 deletions

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
77
javaVersion=25
88
mcVersion=26.1.1
99
group=dev.slne.surf.api
10-
version=3.9.4
10+
version=3.9.5
1111
relocationPrefix=dev.slne.surf.api.libs
1212
snapshot=false

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ adventure-nbt = { module = "net.kyori:adventure-nbt", version.ref = "adventure-a
115115

116116
# Velocity
117117
velocity-api = { module = "com.velocitypowered:velocity-api", version.ref = "velocity-api" }
118+
velocity-proxy-ctd = { module = "com.velocityctd:velocity-proxy", version.ref = "velocity-api"}
118119

119120
# PAPI
120121
placeholder-api = { module = "me.clip:placeholderapi", version.ref = "placeholder-api" }

surf-api-velocity/surf-api-velocity-server/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ dependencies {
1919
api(libs.aide.reflection)
2020
runtimeOnly(libs.flogger.slf4j.backend)
2121
kapt(libs.velocity.api)
22+
compileOnly(libs.velocity.proxy.ctd)
2223
}
2324

2425
tasks {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package dev.slne.surf.api.velocity.server.reflection;
2+
3+
import com.google.common.reflect.TypeToken;
4+
import com.velocitypowered.api.event.EventTask;
5+
import com.velocitypowered.proxy.event.VelocityEventManager;
6+
import org.jspecify.annotations.NullMarked;
7+
8+
import java.lang.invoke.MethodHandle;
9+
import java.lang.invoke.MethodHandles;
10+
import java.lang.invoke.MethodType;
11+
import java.lang.invoke.VarHandle;
12+
import java.lang.reflect.Method;
13+
import java.util.List;
14+
import java.util.function.BiConsumer;
15+
import java.util.function.BiFunction;
16+
import java.util.function.Function;
17+
import java.util.function.Predicate;
18+
19+
@SuppressWarnings("unchecked")
20+
@NullMarked
21+
public final class VelocityEventManagerReflection {
22+
23+
private static final VarHandle HANDLER_ADAPTER;
24+
25+
private static final MethodHandle CUSTOM_HANDLER_ADAPTER_CONSTRUCTOR;
26+
27+
private VelocityEventManagerReflection() {
28+
throw new UnsupportedOperationException();
29+
}
30+
31+
public static List<Object> getHandlerAdapters(VelocityEventManager eventManager) {
32+
return (List<Object>) HANDLER_ADAPTER.get(eventManager);
33+
}
34+
35+
public static <F> Object createCustomHandlerAdapter(String name, Predicate<Method> filter, BiConsumer<Method, List<String>> validator, TypeToken<F> invokeFunctionType, Function<F, BiFunction<Object, Object, EventTask>> handlerBuilder, MethodHandles.Lookup lookup) throws Throwable {
36+
return CUSTOM_HANDLER_ADAPTER_CONSTRUCTOR.invoke(name, filter, validator, invokeFunctionType, handlerBuilder, lookup);
37+
}
38+
39+
static {
40+
try {
41+
Class<?> customHandlerAdapterClass = Class.forName("com.velocitypowered.proxy.event.CustomHandlerAdapter");
42+
43+
MethodHandles.Lookup lookup = MethodHandles.lookup();
44+
MethodHandles.Lookup privateLookupInEventManager = MethodHandles.privateLookupIn(VelocityEventManager.class, lookup);
45+
MethodHandles.Lookup privateLookupInCustomHandlerAdapter = MethodHandles.privateLookupIn(customHandlerAdapterClass, lookup);
46+
47+
HANDLER_ADAPTER = privateLookupInEventManager.findVarHandle(VelocityEventManager.class, "handlerAdapters", List.class);
48+
CUSTOM_HANDLER_ADAPTER_CONSTRUCTOR = privateLookupInCustomHandlerAdapter.findConstructor(customHandlerAdapterClass, MethodType.methodType(void.class, String.class, Predicate.class, BiConsumer.class, TypeToken.class, Function.class, MethodHandles.Lookup.class));
49+
} catch (Exception e) {
50+
throw new ExceptionInInitializerError(e);
51+
}
52+
}
53+
}

surf-api-velocity/surf-api-velocity-server/src/main/kotlin/dev/slne/surf/api/velocity/server/SuspendingEventHandler.kt

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,57 @@ package dev.slne.surf.api.velocity.server
33
import com.google.common.reflect.TypeToken
44
import com.velocitypowered.api.event.EventManager
55
import com.velocitypowered.api.event.EventTask
6-
import dev.slne.surf.api.velocity.server.reflection.VelocityReflection
7-
import java.util.function.BiConsumer
6+
import com.velocitypowered.proxy.event.VelocityEventManager
7+
import dev.slne.surf.api.core.invoker.HiddenInvokerUtil
8+
import dev.slne.surf.api.shared.api.util.InternalInvokerApi
9+
import dev.slne.surf.api.velocity.server.reflection.VelocityEventManagerReflection
10+
import java.lang.invoke.MethodHandles
811
import java.util.function.BiFunction
9-
import java.util.function.Function
10-
import java.util.function.Predicate
1112
import kotlin.coroutines.Continuation
1213
import kotlin.coroutines.EmptyCoroutineContext
1314
import kotlin.coroutines.startCoroutine
14-
import kotlin.reflect.jvm.kotlinFunction
1515
import com.velocitypowered.api.event.Continuation as EventContinuation
1616

17+
1718
class SuspendingEventHandler(private val eventManager: EventManager) {
1819

19-
@Suppress("RedundantSamConstructor")
2020
fun register() {
21-
VelocityReflection.EVENT_MANAGER_PROXY.registerHandlerAdapter(
22-
eventManager,
21+
registerValidationAdapter()
22+
}
23+
24+
@OptIn(InternalInvokerApi::class)
25+
private fun registerValidationAdapter() {
26+
require(eventManager is VelocityEventManager) { "Only VelocityEventManager is supported" }
27+
28+
val handler = VelocityEventManagerReflection.createCustomHandlerAdapter(
2329
"surf_api_suspending_event_handler",
24-
Predicate { method -> method.kotlinFunction?.isSuspend == true },
25-
BiConsumer { method, errors ->
26-
val function = method.kotlinFunction!!
27-
// parameters includes receiver, but excludes continuation
28-
if (function.parameters.size != 2) {
29-
errors += "Expected 1 parameter, but got ${function.parameters.size - 1}. You only need the event parameter."
30-
}
31-
if (function.returnType.classifier != Unit::class) {
32-
errors += "Expected return type of Unit, but got ${function.returnType}."
30+
HiddenInvokerUtil::isSuspendFunction,
31+
{ method, errors ->
32+
if (method.parameterCount != 2) {
33+
errors += "Expected exactly one event parameter, got ${method.parameterCount - 1}."
3334
}
3435
},
3536
object : TypeToken<suspend (Any, Any) -> Unit>() {},
36-
Function { function ->
37+
{ function ->
3738
BiFunction { instance, event ->
3839
suspendingEventTask {
3940
function(instance, event)
4041
}
4142
}
42-
}
43+
},
44+
lookup
4345
)
46+
47+
VelocityEventManagerReflection.getHandlerAdapters(eventManager).add(handler)
4448
}
4549

4650
private fun suspendingEventTask(handler: suspend () -> Unit) =
4751
EventTask.withContinuation { handler.startCoroutine(it.asCoroutineContinuation()) }
4852

4953
private fun EventContinuation.asCoroutineContinuation(): Continuation<Unit> =
5054
Continuation(EmptyCoroutineContext) { if (it.isFailure) resumeWithException(it.exceptionOrNull()) else resume() }
55+
56+
companion object {
57+
private val lookup = MethodHandles.lookup();
58+
}
5159
}

0 commit comments

Comments
 (0)