diff --git a/gradle.properties b/gradle.properties index a5016e5e..a4c71d5d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled javaVersion=25 mcVersion=26.1.1 group=dev.slne.surf.api -version=3.9.4 +version=3.9.5 relocationPrefix=dev.slne.surf.api.libs snapshot=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 68ea4a45..bc3f1bcb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -115,6 +115,7 @@ adventure-nbt = { module = "net.kyori:adventure-nbt", version.ref = "adventure-a # Velocity velocity-api = { module = "com.velocitypowered:velocity-api", version.ref = "velocity-api" } +velocity-proxy-ctd = { module = "com.velocityctd:velocity-proxy", version.ref = "velocity-api"} # PAPI placeholder-api = { module = "me.clip:placeholderapi", version.ref = "placeholder-api" } diff --git a/surf-api-velocity/surf-api-velocity-server/build.gradle.kts b/surf-api-velocity/surf-api-velocity-server/build.gradle.kts index 888501be..2258d6eb 100644 --- a/surf-api-velocity/surf-api-velocity-server/build.gradle.kts +++ b/surf-api-velocity/surf-api-velocity-server/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { api(libs.aide.reflection) runtimeOnly(libs.flogger.slf4j.backend) kapt(libs.velocity.api) + compileOnly(libs.velocity.proxy.ctd) } tasks { diff --git a/surf-api-velocity/surf-api-velocity-server/src/main/java/dev/slne/surf/api/velocity/server/reflection/VelocityEventManagerReflection.java b/surf-api-velocity/surf-api-velocity-server/src/main/java/dev/slne/surf/api/velocity/server/reflection/VelocityEventManagerReflection.java new file mode 100644 index 00000000..7b22da1d --- /dev/null +++ b/surf-api-velocity/surf-api-velocity-server/src/main/java/dev/slne/surf/api/velocity/server/reflection/VelocityEventManagerReflection.java @@ -0,0 +1,53 @@ +package dev.slne.surf.api.velocity.server.reflection; + +import com.google.common.reflect.TypeToken; +import com.velocitypowered.api.event.EventTask; +import com.velocitypowered.proxy.event.VelocityEventManager; +import org.jspecify.annotations.NullMarked; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +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; + +@SuppressWarnings("unchecked") +@NullMarked +public final class VelocityEventManagerReflection { + + private static final VarHandle HANDLER_ADAPTER; + + private static final MethodHandle CUSTOM_HANDLER_ADAPTER_CONSTRUCTOR; + + private VelocityEventManagerReflection() { + throw new UnsupportedOperationException(); + } + + public static List getHandlerAdapters(VelocityEventManager eventManager) { + return (List) HANDLER_ADAPTER.get(eventManager); + } + + public static Object createCustomHandlerAdapter(String name, Predicate filter, BiConsumer> validator, TypeToken invokeFunctionType, Function> handlerBuilder, MethodHandles.Lookup lookup) throws Throwable { + return CUSTOM_HANDLER_ADAPTER_CONSTRUCTOR.invoke(name, filter, validator, invokeFunctionType, handlerBuilder, lookup); + } + + static { + try { + Class customHandlerAdapterClass = Class.forName("com.velocitypowered.proxy.event.CustomHandlerAdapter"); + + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandles.Lookup privateLookupInEventManager = MethodHandles.privateLookupIn(VelocityEventManager.class, lookup); + MethodHandles.Lookup privateLookupInCustomHandlerAdapter = MethodHandles.privateLookupIn(customHandlerAdapterClass, lookup); + + HANDLER_ADAPTER = privateLookupInEventManager.findVarHandle(VelocityEventManager.class, "handlerAdapters", List.class); + CUSTOM_HANDLER_ADAPTER_CONSTRUCTOR = privateLookupInCustomHandlerAdapter.findConstructor(customHandlerAdapterClass, MethodType.methodType(void.class, String.class, Predicate.class, BiConsumer.class, TypeToken.class, Function.class, MethodHandles.Lookup.class)); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } +} diff --git a/surf-api-velocity/surf-api-velocity-server/src/main/kotlin/dev/slne/surf/api/velocity/server/SuspendingEventHandler.kt b/surf-api-velocity/surf-api-velocity-server/src/main/kotlin/dev/slne/surf/api/velocity/server/SuspendingEventHandler.kt index 05e28e62..d9bba29c 100644 --- a/surf-api-velocity/surf-api-velocity-server/src/main/kotlin/dev/slne/surf/api/velocity/server/SuspendingEventHandler.kt +++ b/surf-api-velocity/surf-api-velocity-server/src/main/kotlin/dev/slne/surf/api/velocity/server/SuspendingEventHandler.kt @@ -3,44 +3,48 @@ package dev.slne.surf.api.velocity.server import com.google.common.reflect.TypeToken import com.velocitypowered.api.event.EventManager import com.velocitypowered.api.event.EventTask -import dev.slne.surf.api.velocity.server.reflection.VelocityReflection -import java.util.function.BiConsumer +import com.velocitypowered.proxy.event.VelocityEventManager +import dev.slne.surf.api.core.invoker.HiddenInvokerUtil +import dev.slne.surf.api.shared.api.util.InternalInvokerApi +import dev.slne.surf.api.velocity.server.reflection.VelocityEventManagerReflection +import java.lang.invoke.MethodHandles import java.util.function.BiFunction -import java.util.function.Function -import java.util.function.Predicate import kotlin.coroutines.Continuation import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.startCoroutine -import kotlin.reflect.jvm.kotlinFunction import com.velocitypowered.api.event.Continuation as EventContinuation + class SuspendingEventHandler(private val eventManager: EventManager) { - @Suppress("RedundantSamConstructor") fun register() { - VelocityReflection.EVENT_MANAGER_PROXY.registerHandlerAdapter( - eventManager, + registerValidationAdapter() + } + + @OptIn(InternalInvokerApi::class) + private fun registerValidationAdapter() { + require(eventManager is VelocityEventManager) { "Only VelocityEventManager is supported" } + + val handler = VelocityEventManagerReflection.createCustomHandlerAdapter( "surf_api_suspending_event_handler", - Predicate { method -> method.kotlinFunction?.isSuspend == true }, - BiConsumer { method, errors -> - val function = method.kotlinFunction!! - // parameters includes receiver, but excludes continuation - if (function.parameters.size != 2) { - errors += "Expected 1 parameter, but got ${function.parameters.size - 1}. You only need the event parameter." - } - if (function.returnType.classifier != Unit::class) { - errors += "Expected return type of Unit, but got ${function.returnType}." + HiddenInvokerUtil::isSuspendFunction, + { method, errors -> + if (method.parameterCount != 2) { + errors += "Expected exactly one event parameter, got ${method.parameterCount - 1}." } }, object : TypeToken Unit>() {}, - Function { function -> + { function -> BiFunction { instance, event -> suspendingEventTask { function(instance, event) } } - } + }, + lookup ) + + VelocityEventManagerReflection.getHandlerAdapters(eventManager).add(handler) } private fun suspendingEventTask(handler: suspend () -> Unit) = @@ -48,4 +52,8 @@ class SuspendingEventHandler(private val eventManager: EventManager) { private fun EventContinuation.asCoroutineContinuation(): Continuation = Continuation(EmptyCoroutineContext) { if (it.isFailure) resumeWithException(it.exceptionOrNull()) else resume() } + + companion object { + private val lookup = MethodHandles.lookup(); + } } \ No newline at end of file