diff --git a/build.gradle.kts b/build.gradle.kts index ce1833ab..3e8951f5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,7 @@ subprojects { afterEvaluate { extensions.findByType()?.apply { compilerOptions { - optIn.add("dev.slne.surf.surfapi.core.api.util.InternalSurfApi") + optIn.add("dev.slne.surf.surfapi.shared.api.util.InternalSurfApi") } } } diff --git a/buildSrc/src/main/kotlin/core-convention.gradle.kts b/buildSrc/src/main/kotlin/core-convention.gradle.kts index 59d2424a..744f7e82 100644 --- a/buildSrc/src/main/kotlin/core-convention.gradle.kts +++ b/buildSrc/src/main/kotlin/core-convention.gradle.kts @@ -27,7 +27,9 @@ repositories { dependencies { compileOnly(libs.auto.service.annotations) - ksp(project(":surf-api-gradle-plugin:surf-api-processor")) + if (!project.path.contains("surf-api-shared")) { + ksp(project(":surf-api-gradle-plugin:surf-api-processor")) + } compileOnlyApi("org.jetbrains:annotations:26.0.2-1") } diff --git a/gradle.properties b/gradle.properties index 102869b5..690b873b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled javaVersion=25 mcVersion=1.21.11 group=dev.slne.surf -version=1.21.11-2.55.1 +version=1.21.11-2.56.0 relocationPrefix=dev.slne.surf.surfapi.libs snapshot=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8bdaf60c..d997cfc6 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e111328..6e750047 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-milestone-5-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index ef07e016..0fff279d 100755 --- a/gradlew +++ b/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/dd2cf7f3826b2da07d8d6de488a2d1bc5651ca7d/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index db3a6ac2..c4bdd3ab 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle.kts b/settings.gradle.kts index 49471d0f..c5d1fb12 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,4 +31,7 @@ if (!ci) { include(":surf-api-bukkit:surf-api-bukkit-plugin-test") // include("surf-api-generator") include("surf-api-modern-generator") -} \ No newline at end of file +} +include("surf-api-shared") +include("surf-api-shared:surf-api-shared-public") +include("surf-api-shared:surf-api-shared-internal") \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/command/executors/SuspendCommandExecutors.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/command/executors/SuspendCommandExecutors.kt index 5e06e2da..dcaa0672 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/command/executors/SuspendCommandExecutors.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/command/executors/SuspendCommandExecutors.kt @@ -13,7 +13,7 @@ import dev.jorel.commandapi.kotlindsl.* import dev.jorel.commandapi.wrappers.NativeProxyCommandSender import dev.slne.surf.surfapi.core.api.messages.Colors import dev.slne.surf.surfapi.core.api.messages.adventure.text -import dev.slne.surf.surfapi.core.api.util.InternalSurfApi +import dev.slne.surf.surfapi.shared.api.util.InternalSurfApi import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.kt b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.kt index 6d368cc9..af4c1a99 100644 --- a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.kt +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.kt @@ -10,24 +10,29 @@ import dev.slne.surf.surfapi.bukkit.test.command.subcommands.inventory.TestInven import dev.slne.surf.surfapi.bukkit.test.command.subcommands.reflection.Reflection import dev.slne.surf.surfapi.bukkit.test.config.ModernTestConfig import dev.slne.surf.surfapi.bukkit.test.listener.ChatListener +import dev.slne.surf.surfapi.core.api.hook.surfHookApi @OptIn(NmsUseWithCaution::class) class BukkitPluginMain : SuspendingJavaPlugin() { - override fun onLoad() { + override suspend fun onLoadAsync() { ModernTestConfig.init() ModernTestConfig.randomise() + surfHookApi.load(this) packetListenerApi.registerListeners(ChatListener()) TestInventoryView.register() } - override fun onEnable() { + override suspend fun onEnableAsync() { SurfApiTestCommand().register() Reflection::class.java.getClassLoader() // initialize Reflection + + surfHookApi.enable(this) } - override fun onDisable() { + override suspend fun onDisableAsync() { CommandAPI.unregister("surfapitest") + surfHookApi.disable(this) } companion object { diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/hook/PrimaryTestHook.kt b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/hook/PrimaryTestHook.kt new file mode 100644 index 00000000..feb9071f --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/hook/PrimaryTestHook.kt @@ -0,0 +1,29 @@ +package dev.slne.surf.surfapi.bukkit.test.hook + +import dev.slne.surf.surfapi.bukkit.test.hook.condition.EnabledCondition +import dev.slne.surf.surfapi.core.api.hook.AbstractHook +import dev.slne.surf.surfapi.core.api.util.logger +import dev.slne.surf.surfapi.shared.api.hook.HookMeta +import dev.slne.surf.surfapi.shared.api.hook.requirement.ConditionalOnCustom + +@ConditionalOnCustom(EnabledCondition::class) +@HookMeta +class PrimaryTestHook : AbstractHook() { + private val log = logger() + + override suspend fun onBootstrap() { + log.atInfo().log("PrimaryTestHook bootstrapped") + } + + override suspend fun onLoad() { + log.atInfo().log("PrimaryTestHook loaded") + } + + override suspend fun onEnable() { + log.atInfo().log("PrimaryTestHook enabled") + } + + override suspend fun onDisable() { + log.atInfo().log("PrimaryTestHook disabled") + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/hook/TestHook.kt b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/hook/TestHook.kt new file mode 100644 index 00000000..db168398 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/hook/TestHook.kt @@ -0,0 +1,33 @@ +package dev.slne.surf.surfapi.bukkit.test.hook + +import dev.slne.surf.surfapi.bukkit.test.BukkitPluginMain +import dev.slne.surf.surfapi.core.api.hook.AbstractHook +import dev.slne.surf.surfapi.core.api.util.logger +import dev.slne.surf.surfapi.shared.api.hook.HookMeta +import dev.slne.surf.surfapi.shared.api.hook.requirement.* + +@HookMeta +@DependsOnClass(BukkitPluginMain::class) +@DependsOnClassName("dev.slne.surf.surfapi.bukkit.test.config.ModernTestConfig") +@DependsOnPlugin("SurfBukkitPluginTest") +@DependsOnOnePlugin(["SurfBukkitPlugin", "surf-bukkit-plugin", "SurfBukkitPluginTest"]) +@DependsOnHook(PrimaryTestHook::class) +class TestHook : AbstractHook() { + private val log = logger() + + override suspend fun onBootstrap() { + log.atInfo().log("TestHook bootstrapped") + } + + override suspend fun onLoad() { + log.atInfo().log("TestHook loaded") + } + + override suspend fun onEnable() { + log.atInfo().log("TestHook enabled") + } + + override suspend fun onDisable() { + log.atInfo().log("TestHook disabled") + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/hook/condition/EnabledCondition.kt b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/hook/condition/EnabledCondition.kt new file mode 100644 index 00000000..aa98ca8b --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/hook/condition/EnabledCondition.kt @@ -0,0 +1,11 @@ +package dev.slne.surf.surfapi.bukkit.test.hook.condition + +import dev.slne.surf.surfapi.bukkit.test.config.ModernTestConfig +import dev.slne.surf.surfapi.shared.api.hook.condition.HookCondition +import dev.slne.surf.surfapi.shared.api.hook.condition.HookConditionContext + +class EnabledCondition : HookCondition { + override suspend fun evaluate(context: HookConditionContext): Boolean { + return ModernTestConfig.getConfig().enabled + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/hook/PaperHookService.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/hook/PaperHookService.kt new file mode 100644 index 00000000..d87c0a13 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/hook/PaperHookService.kt @@ -0,0 +1,42 @@ +package dev.slne.surf.surfapi.bukkit.server.hook + +import com.google.auto.service.AutoService +import dev.slne.surf.surfapi.bukkit.api.extensions.pluginManager +import dev.slne.surf.surfapi.bukkit.server.reflection.Reflection +import dev.slne.surf.surfapi.core.server.hook.HookService +import net.kyori.adventure.text.logger.slf4j.ComponentLogger +import org.bukkit.plugin.java.JavaPlugin +import java.io.InputStream +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +@AutoService(HookService::class) +class PaperHookService : HookService() { + override fun readHooksFileFromResources(owner: Any, fileName: String): InputStream? { + ensureOwnerIsPlugin(owner) + return owner.getResource(fileName) + } + + override fun getClassloader(owner: Any): ClassLoader { + ensureOwnerIsPlugin(owner) + return Reflection.JAVA_PLUGIN_PROXY.getClassLoader(owner) + } + + override fun isPluginLoaded(pluginId: String): Boolean { + return pluginManager.getPlugin(pluginId) != null + } + + override fun getLogger(owner: Any): ComponentLogger { + ensureOwnerIsPlugin(owner) + return owner.componentLogger + } + + @OptIn(ExperimentalContracts::class) + private fun ensureOwnerIsPlugin(owner: Any): JavaPlugin { + contract { + returns() implies (owner is JavaPlugin) + } + + return owner as? JavaPlugin ?: error("Owner must be a JavaPlugin") + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/glow/entity/EntityGlowingData.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/glow/entity/EntityGlowingData.kt index 409263dd..8bfd098f 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/glow/entity/EntityGlowingData.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/glow/entity/EntityGlowingData.kt @@ -35,7 +35,7 @@ data class EntityGlowingData( @OptIn(NmsUseWithCaution::class) fun removeFromTeam(): PacketOperation { val color = color ?: return PacketOperationImpl.empty() - val teamData = TeamData.Companion.getByColorOrNull(color) ?: return PacketOperationImpl.empty() + val teamData = TeamData.getByColorOrNull(color) ?: return PacketOperationImpl.empty() val operation = PacketOperation.start() if (teamData.removeSeen(playerData.uuid)) { diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/JavaPluginProxy.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/JavaPluginProxy.kt new file mode 100644 index 00000000..74b0ac76 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/JavaPluginProxy.kt @@ -0,0 +1,12 @@ +package dev.slne.surf.surfapi.bukkit.server.reflection + +import dev.slne.surf.surfapi.core.api.reflection.Name +import dev.slne.surf.surfapi.core.api.reflection.SurfProxy +import org.bukkit.plugin.java.JavaPlugin + +@SurfProxy(JavaPlugin::class) +interface JavaPluginProxy { + + @Name("getClassLoader") + fun getClassLoader(instance: JavaPlugin): ClassLoader +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/Reflection.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/Reflection.kt index 953ec7a6..02aece6a 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/Reflection.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/Reflection.kt @@ -11,16 +11,18 @@ object Reflection { val ITEM_PROXY: ItemProxy val ENTITY_PROXY: EntityProxy val SERVER_CONNECTION_LISTENER_PROXY: ServerConnectionListenerProxy + val JAVA_PLUGIN_PROXY: JavaPluginProxy init { val remapper = ReflectionRemapper.forReobfMappingsInPaperJar() val proxyFactory = - ReflectionProxyFactory.create(remapper, Reflection::class.java.getClassLoader()) + ReflectionProxyFactory.create(remapper, Reflection::class.java.classLoader) SERVER_STATS_COUNTER_PROXY = proxyFactory.reflectionProxy() ITEM_PROXY = surfReflection.createProxy() ENTITY_PROXY = proxyFactory.reflectionProxy() SERVER_CONNECTION_LISTENER_PROXY = proxyFactory.reflectionProxy() + JAVA_PLUGIN_PROXY = surfReflection.createProxy() // gc the remapper System.gc() diff --git a/surf-api-core/surf-api-core-api/api/surf-api-core-api.api b/surf-api-core/surf-api-core-api/api/surf-api-core-api.api index fa0651a2..4a7e58da 100644 --- a/surf-api-core/surf-api-core-api/api/surf-api-core-api.api +++ b/surf-api-core/surf-api-core-api/api/surf-api-core-api.api @@ -23,6 +23,11 @@ public final class dev/slne/surf/surfapi/core/api/algorithms/ConvexHull2DKt { public static final fun convexHull2D ([Lorg/spongepowered/math/vector/Vectord;)Lit/unimi/dsi/fastutil/objects/ObjectList; } +public final class dev/slne/surf/surfapi/core/api/algorithms/Kahn_topological_sortKt { + public static final fun topologicalSort (Ljava/util/Map;)Ljava/util/List; + public static final fun topologicalSortSafe (Ljava/util/Map;)Ljava/lang/Object; +} + public final class dev/slne/surf/surfapi/core/api/collection/TransformingObjectSet : it/unimi/dsi/fastutil/objects/ObjectSet { public fun (Lit/unimi/dsi/fastutil/objects/ObjectSet;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V public fun add (Ljava/lang/Object;)Z @@ -6389,6 +6394,43 @@ public final class dev/slne/surf/surfapi/core/api/generated/VanillaAdvancementKe public static final field UPGRADE_TOOLS Lnet/kyori/adventure/key/Key; } +public abstract class dev/slne/surf/surfapi/core/api/hook/AbstractHook : dev/slne/surf/surfapi/shared/api/hook/Hook { + public fun ()V + public final fun bootstrap (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun compareTo (Ldev/slne/surf/surfapi/shared/api/hook/Hook;)I + public synthetic fun compareTo (Ljava/lang/Object;)I + public final fun disable (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun enable (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getPriority ()S + public final fun load (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected fun onBootstrap (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected fun onDisable (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected fun onEnable (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected fun onLoad (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface class dev/slne/surf/surfapi/core/api/hook/SurfHookApi { + public static final field Companion Ldev/slne/surf/surfapi/core/api/hook/SurfHookApi$Companion; + public abstract fun bootstrap (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun disable (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun enable (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun hooks (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun hooksLoaded (Ljava/lang/Object;)Ljava/util/List; + public abstract fun hooksOfType (Ljava/lang/Class;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun hooksOfType (Ljava/lang/Object;Ljava/lang/Class;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun hooksOfTypeLoaded (Ljava/lang/Class;)Ljava/util/List; + public abstract fun hooksOfTypeLoaded (Ljava/lang/Object;Ljava/lang/Class;)Ljava/util/List; + public abstract fun load (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/slne/surf/surfapi/core/api/hook/SurfHookApi$Companion { + public final fun getInstance ()Ldev/slne/surf/surfapi/core/api/hook/SurfHookApi; +} + +public final class dev/slne/surf/surfapi/core/api/hook/SurfHookApiKt { + public static final fun getSurfHookApi ()Ldev/slne/surf/surfapi/core/api/hook/SurfHookApi; +} + public final class dev/slne/surf/surfapi/core/api/math/VoxelLineTracer { public static final field INSTANCE Ldev/slne/surf/surfapi/core/api/math/VoxelLineTracer; public final fun trace (Lorg/spongepowered/math/vector/Vector3d;Lorg/spongepowered/math/vector/Vector3d;)Lkotlin/sequences/Sequence; @@ -10214,9 +10256,6 @@ public final class dev/slne/surf/surfapi/core/api/util/Fast_util_utilKt { public static final fun toShortSet ([Ljava/lang/Short;)Lit/unimi/dsi/fastutil/shorts/ShortSet; } -public abstract interface annotation class dev/slne/surf/surfapi/core/api/util/InternalSurfApi : java/lang/annotation/Annotation { -} - public abstract interface class dev/slne/surf/surfapi/core/api/util/ItemStackFactory { public static final field Companion Ldev/slne/surf/surfapi/core/api/util/ItemStackFactory$Companion; } diff --git a/surf-api-core/surf-api-core-api/build.gradle.kts b/surf-api-core/surf-api-core-api/build.gradle.kts index 8ad624f2..9cfc75e1 100644 --- a/surf-api-core/surf-api-core-api/build.gradle.kts +++ b/surf-api-core/surf-api-core-api/build.gradle.kts @@ -4,13 +4,7 @@ plugins { } dependencies { - compileOnlyApi(libs.adventure.api) - compileOnlyApi(libs.adventure.text.logger.slf4j) - compileOnlyApi(libs.adventure.text.minimessage) - compileOnlyApi(libs.adventure.serializer.gson) - compileOnlyApi(libs.adventure.serializer.legacy) - compileOnlyApi(libs.adventure.serializer.plain) - compileOnlyApi(libs.adventure.serializer.ansi) + api(project(":surf-api-shared:surf-api-shared-public")) api(libs.adventure.nbt) compileOnlyApi(libs.packetevents.api) compileOnlyApi(libs.dazzleconf) @@ -31,8 +25,6 @@ dependencies { api(libs.caffeine.courotines) api(libs.bundles.kotlin.coroutines) api(libs.bundles.reactor.netty) - api(libs.kotlin.reflect) - api(libs.bundles.kotlin.serialization) compileOnlyApi(libs.guava) compileOnlyApi(libs.caffeine) @@ -46,12 +38,6 @@ dependencies { api(libs.datafixerupper) { isTransitive = false } } -kotlin { - compilerOptions { - optIn.add("dev.slne.surf.surfapi.core.api.util.InternalSurfApi") - } -} - tasks { shadowJar { val relocationPrefix: String by project diff --git a/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/algorithms/kahn-topological-sort.kt b/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/algorithms/kahn-topological-sort.kt new file mode 100644 index 00000000..0f9e0a3d --- /dev/null +++ b/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/algorithms/kahn-topological-sort.kt @@ -0,0 +1,51 @@ +package dev.slne.surf.surfapi.core.api.algorithms + +import dev.slne.surf.surfapi.core.api.util.mutableObject2IntMapOf +import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf + +private typealias Graph = Map> + +fun Graph.topologicalSortSafe(): Result> { + val graph = this + val incomingEdges = mutableObject2IntMapOf() + for ((vertex, successors) in graph) { + if (vertex !in incomingEdges) { + incomingEdges[vertex] = 0 + } + for (successor in successors) { + incomingEdges.mergeInt(successor, 1, Int::plus) + } + } + + val queue = ArrayDeque() + incomingEdges.object2IntEntrySet().fastForEach {entry -> + val vertex = entry.key + val edges = entry.intValue + if (edges == 0) queue += vertex + + } + + val result = mutableObjectListOf() + + while (queue.isNotEmpty()) { + val vertex = queue.removeFirst() + result += vertex + + for (successor in graph[vertex].orEmpty()) { + incomingEdges.mergeInt(successor, -1, Int::minus) + if (incomingEdges.getInt(successor) == 0) { + queue += successor + } + } + } + + if (result.size != incomingEdges.size) { + return Result.failure(IllegalStateException("Graph contains a cycle, topological sort not possible!")) + } + + return Result.success(result) +} + +fun Graph.topologicalSort(): List { + return topologicalSortSafe().getOrThrow() +} diff --git a/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/hook/AbstractHook.kt b/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/hook/AbstractHook.kt new file mode 100644 index 00000000..e4a3a698 --- /dev/null +++ b/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/hook/AbstractHook.kt @@ -0,0 +1,57 @@ +package dev.slne.surf.surfapi.core.api.hook + +import dev.slne.surf.surfapi.shared.api.hook.Hook +import dev.slne.surf.surfapi.shared.api.hook.HookMeta +import dev.slne.surf.surfapi.shared.api.util.InternalSurfApi +import java.util.concurrent.atomic.AtomicBoolean + +abstract class AbstractHook : Hook { + private val bootstrapped = AtomicBoolean(false) + private val loaded = AtomicBoolean(false) + private val enabled = AtomicBoolean(false) + private val disabled = AtomicBoolean(false) + + private val meta: HookMeta = javaClass.getAnnotation(HookMeta::class.java) + ?: error("HookMeta annotation is missing on hook class ${this::class.qualifiedName}") + + final override val priority = meta.priority + + @InternalSurfApi + final override suspend fun bootstrap() { + if (bootstrapped.compareAndSet(false, true)) { + onBootstrap() + } + } + + @InternalSurfApi + final override suspend fun load() { + if (loaded.compareAndSet(false, true)) { + bootstrap() + onLoad() + } + } + + @InternalSurfApi + final override suspend fun enable() { + if (enabled.compareAndSet(false, true)) { + load() + onEnable() + } + } + + @InternalSurfApi + final override suspend fun disable() { + if (disabled.compareAndSet(false, true)) { + onDisable() + } + } + + final override fun compareTo(other: Hook): Int { + return this.priority.compareTo(other.priority) + } + + protected open suspend fun onBootstrap() {} + protected open suspend fun onLoad() {} + protected open suspend fun onEnable() {} + protected open suspend fun onDisable() {} +} \ No newline at end of file diff --git a/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/hook/SurfHookApi.kt b/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/hook/SurfHookApi.kt new file mode 100644 index 00000000..d65cb1f3 --- /dev/null +++ b/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/hook/SurfHookApi.kt @@ -0,0 +1,27 @@ +package dev.slne.surf.surfapi.core.api.hook + +import dev.slne.surf.surfapi.core.api.util.requiredService +import dev.slne.surf.surfapi.shared.api.hook.Hook + +interface SurfHookApi { + + suspend fun bootstrap(owner: Any) + suspend fun load(owner: Any) + suspend fun enable(owner: Any) + suspend fun disable(owner: Any) + + suspend fun hooksOfType(owner: Any, type: Class): List + fun hooksOfTypeLoaded(owner: Any, type: Class): List + suspend fun hooksOfType(type: Class): List + fun hooksOfTypeLoaded(type: Class): List + + suspend fun hooks(owner: Any): List + fun hooksLoaded(owner: Any): List + + companion object { + val instance = requiredService() + } +} + +val surfHookApi get() = SurfHookApi.instance + diff --git a/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/messages/pagination/InternalPaginationBridge.kt b/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/messages/pagination/InternalPaginationBridge.kt index 3989961e..b5ad7d07 100644 --- a/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/messages/pagination/InternalPaginationBridge.kt +++ b/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/messages/pagination/InternalPaginationBridge.kt @@ -1,7 +1,7 @@ package dev.slne.surf.surfapi.core.api.messages.pagination -import dev.slne.surf.surfapi.core.api.util.InternalSurfApi import dev.slne.surf.surfapi.core.api.util.requiredService +import dev.slne.surf.surfapi.shared.api.util.InternalSurfApi @InternalSurfApi interface InternalPaginationBridge { diff --git a/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/nbt/InternalNbtBridge.kt b/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/nbt/InternalNbtBridge.kt index 6bf36284..623cd6e2 100644 --- a/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/nbt/InternalNbtBridge.kt +++ b/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/nbt/InternalNbtBridge.kt @@ -1,7 +1,7 @@ package dev.slne.surf.surfapi.core.api.nbt -import dev.slne.surf.surfapi.core.api.util.InternalSurfApi import dev.slne.surf.surfapi.core.api.util.requiredService +import dev.slne.surf.surfapi.shared.api.util.InternalSurfApi import net.kyori.adventure.nbt.CompoundBinaryTag @InternalSurfApi diff --git a/surf-api-core/surf-api-core-server/build.gradle.kts b/surf-api-core/surf-api-core-server/build.gradle.kts index 97f09634..ccf9857b 100644 --- a/surf-api-core/surf-api-core-server/build.gradle.kts +++ b/surf-api-core/surf-api-core-server/build.gradle.kts @@ -4,14 +4,8 @@ plugins { dependencies { api(project(":surf-api-core:surf-api-core-api")) + api(project(":surf-api-shared:surf-api-shared-internal")) compileOnly(libs.packetevents.netty.common) } - -kotlin { - compilerOptions { - optIn.add("dev.slne.surf.surfapi.core.api.util.InternalSurfApi") - } -} - description = "surf-api-core-server" diff --git a/surf-api-core/surf-api-core-server/src/main/java/dev/slne/surf/surfapi/core/server/impl/reflection/SurfInvocationHandlerJava.java b/surf-api-core/surf-api-core-server/src/main/java/dev/slne/surf/surfapi/core/server/impl/reflection/SurfInvocationHandlerJava.java index 5ef069c5..ad4ceb85 100644 --- a/surf-api-core/surf-api-core-server/src/main/java/dev/slne/surf/surfapi/core/server/impl/reflection/SurfInvocationHandlerJava.java +++ b/surf-api-core/surf-api-core-server/src/main/java/dev/slne/surf/surfapi/core/server/impl/reflection/SurfInvocationHandlerJava.java @@ -6,6 +6,12 @@ import dev.slne.surf.surfapi.core.api.util.SurfUtil; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.commons.lang3.reflect.MethodUtils; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -19,11 +25,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Collectors; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.reflect.FieldUtils; -import org.apache.commons.lang3.reflect.MethodUtils; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; @NullMarked public final class SurfInvocationHandlerJava implements InvocationHandler { @@ -98,6 +99,7 @@ private Invokable createInvokable(final Method method) { final var constructorAnnotation = method.getDeclaredAnnotation( dev.slne.surf.surfapi.core.api.reflection.Constructor.class); final var nameAnnotation = method.getDeclaredAnnotation(Name.class); + final var privateLookup = sneaky(() -> MethodHandles.privateLookupIn(proxiedClass, LOOKUP)); if (fieldAnnotation != null) { final String fieldName = getMethodName(method, nameAnnotation, fieldAnnotation, @@ -105,9 +107,9 @@ private Invokable createInvokable(final Method method) { final Field field = sneaky(() -> findField(proxiedClass, fieldName)); final boolean isGetter = fieldAnnotation.type() == Type.GETTER; final MethodHandle handleGetter = - isGetter ? sneaky(() -> LOOKUP.unreflectGetter(field)) : null; + isGetter ? sneaky(() -> privateLookup.unreflectGetter(field)) : null; final MethodHandle handleSetter = !isGetter && !fieldAnnotation.overrideFinal() - ? sneaky(() -> LOOKUP.unreflectSetter(field)) : null; + ? sneaky(() -> privateLookup.unreflectSetter(field)) : null; if (isGetter) { checkParamCount(method, staticAnnotation != null ? 0 : 1); @@ -125,7 +127,7 @@ private Invokable createInvokable(final Method method) { if (constructorAnnotation != null) { final var handle = sneaky( - () -> LOOKUP.unreflectConstructor(findConstructor(proxiedClass, method))); + () -> privateLookup.unreflectConstructor(findConstructor(proxiedClass, method))); return new HandleInvokable(normalizeMethodHandleType(handle)); } @@ -136,7 +138,7 @@ private Invokable createInvokable(final Method method) { final Method target = sneaky( () -> findMethod(proxiedClass, method, nameAnnotation, staticAnnotation)); - final MethodHandle handle = sneaky(() -> LOOKUP.unreflect(target)); + final MethodHandle handle = sneaky(() -> privateLookup.unreflect(target)); return new HandleInvokable(normalizeMethodHandleType(handle)); } @@ -176,7 +178,7 @@ private static Method findMethod( final Class[] paramTypes = Arrays.copyOfRange(original.getParameterTypes(), paramOffset, original.getParameterCount()); final String methodName = getMethodName(original, nameAnnotation, null, staticAnnotation, null); - final Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, paramTypes); + final Method method = MethodUtils.getMatchingMethod(clazz, methodName, paramTypes); if (method == null) { throw new NoSuchMethodException( "Method " + methodName + " with params " + Arrays.toString(paramTypes)); diff --git a/surf-api-core/surf-api-core-server/src/main/kotlin/dev/slne/surf/surfapi/core/server/hook/HookService.kt b/surf-api-core/surf-api-core-server/src/main/kotlin/dev/slne/surf/surfapi/core/server/hook/HookService.kt new file mode 100644 index 00000000..fd29e71a --- /dev/null +++ b/surf-api-core/surf-api-core-server/src/main/kotlin/dev/slne/surf/surfapi/core/server/hook/HookService.kt @@ -0,0 +1,307 @@ +package dev.slne.surf.surfapi.core.server.hook + +import com.github.benmanes.caffeine.cache.Caffeine +import com.sksamuel.aedile.core.asLoadingCache +import dev.slne.surf.surfapi.core.api.util.* +import dev.slne.surf.surfapi.shared.api.hook.Hook +import dev.slne.surf.surfapi.shared.api.hook.condition.HookCondition +import dev.slne.surf.surfapi.shared.api.hook.condition.HookConditionContext +import dev.slne.surf.surfapi.shared.internal.hook.HooksConfig +import dev.slne.surf.surfapi.shared.internal.hook.PluginHookMeta +import kotlinx.serialization.SerializationException +import net.kyori.adventure.text.logger.slf4j.ComponentLogger +import java.io.InputStream +import java.util.* + +abstract class HookService { + + private val hookMetaCache = Caffeine.newBuilder() + .weakKeys() + .build { owner -> loadHooksMeta(owner) } + + private val hooksCache = Caffeine.newBuilder() + .weakKeys() + .asLoadingCache { owner -> loadHooks(owner) } + + private fun loadHooksMeta(owner: Any): PluginHookMeta { + val rawStream = readHooksFileFromResources(owner, HooksConfig.HOOKS_FILE_NAME) ?: return PluginHookMeta.empty() + val raw = rawStream.bufferedReader().use { it.readText() } + return try { + HooksConfig.json.decodeFromString(raw) + } catch (e: SerializationException) { + getLogger(owner).error("Failed to parse ${HooksConfig.HOOKS_FILE_NAME}", e) + PluginHookMeta.empty() + } + } + + private suspend fun loadHooks(owner: Any): List { + val meta = hookMetaCache.get(owner) + val classLoader = getClassloader(owner) + + val hooksWithMeta = meta.hooks.mapNotNull { hookMeta -> + val hook = instantiateHookIfValid(owner, hookMeta, classLoader) + if (hook != null) { + hookMeta to hook + } else { + null + } + } + + return topologicalSort(hooksWithMeta, owner) + } + + private suspend fun instantiateHookIfValid( + owner: Any, + hookMeta: PluginHookMeta.Hook, + classLoader: ClassLoader + ): Hook? { + val missingDependencies = mutableObject2ObjectMapOf>() + for (classDependency in hookMeta.classDependencies) { + try { + Class.forName(classDependency, false, classLoader) + } catch (_: ClassNotFoundException) { + missingDependencies.computeIfAbsent("Class") { mutableObjectSetOf() }.add(classDependency) + } + } + + for (pluginDependencyId in hookMeta.pluginDependencies) { + if (!isPluginLoaded(pluginDependencyId)) { + missingDependencies.computeIfAbsent("Plugin") { mutableObjectSetOf() }.add(pluginDependencyId) + } + } + + for (pluginDependenciesIds in hookMeta.pluginOneDependencies) { + if (pluginDependenciesIds.none { isPluginLoaded(it) }) { + missingDependencies.computeIfAbsent("Plugin (one of)") { mutableObjectSetOf() } + .add(pluginDependenciesIds.joinToString("|")) + } + } + + if (missingDependencies.isNotEmpty()) { + logMissingDependencies(owner, hookMeta.className, missingDependencies) + return null + } + + if (!evaluateConditions(owner, hookMeta, classLoader)) return null + + try { + val hookClass = Class.forName(hookMeta.className, false, classLoader) + val hookKClass = hookClass.kotlin + val objectInstance = hookKClass.objectInstance + if (objectInstance != null) { + require(objectInstance is Hook) { "Hook class must implement Hook" } + return objectInstance + } else { + val constructor = hookClass.getConstructor() + val instance = constructor.newInstance() + require(instance is Hook) { "Hook class must implement Hook" } + return instance + } + } catch (e: Exception) { + getLogger(owner).error("Failed to load hook ${hookMeta.className}", e) + } + + return null + } + + @Suppress("UNCHECKED_CAST") + private suspend fun evaluateConditions( + owner: Any, + hookMeta: PluginHookMeta.Hook, + classLoader: ClassLoader + ): Boolean { + for (conditionClassName in hookMeta.customConditions) { + try { + val conditionClass = Class.forName(conditionClassName, false, classLoader) + val condition = conditionClass.getConstructor().newInstance() as HookCondition + val logger = getLogger(owner) + + val context = HookConditionContext( + owner = owner, + logger = logger, + hookClass = Class.forName(hookMeta.className, false, classLoader) as Class + ) + + if (!condition.evaluate(context)) { + logger.debug("Hook ${hookMeta.className} skipped due to condition $conditionClassName") + return false + } + } catch (e: Exception) { + getLogger(owner).error("Failed to evaluate condition $conditionClassName", e) + return false + } + } + return true + } + + private fun topologicalSort( + hooksWithMeta: List>, + owner: Any + ): List { + // If no hooks depend on other hooks, simply sort by priority + if (hooksWithMeta.none { it.first.hookDependencies.isNotEmpty() }) { + return hooksWithMeta.map { it.second }.sorted() + } + + val hooksByClassName = hooksWithMeta.associate { (meta, hook) -> + meta.className to hook + } + + val metaByClassName = hooksWithMeta.associate { (meta, _) -> + meta.className to meta + } + + val missingHookDeps = mutableMapOf>() + for ((meta, _) in hooksWithMeta) { + for (depClassName in meta.hookDependencies) { + if (depClassName !in hooksByClassName) { + missingHookDeps.computeIfAbsent(meta.className) { mutableSetOf() } + .add(depClassName) + } + } + } + + if (missingHookDeps.isNotEmpty()) { + val logger = getLogger(owner) + for ((hookClassName, missingDeps) in missingHookDeps) { + logger.warn( + "Hook $hookClassName depends on hooks that are not loaded: ${missingDeps.joinToString(", ")}" + ) + } + } + + val validHooks = hooksWithMeta.filter { (meta, _) -> + meta.className !in missingHookDeps + } + + if (validHooks.isEmpty()) { + return emptyList() + } + + // Build dependency graph: className -> list of dependents (successors) + // Note: In Kahn's algorithm, edges go from dependency to dependent + val graph = mutableObject2ObjectMapOf>() + for ((meta, _) in validHooks) { + // Ensure all nodes exist in the graph + if (meta.className !in graph) { + graph[meta.className] = mutableListOf() + } + // Add edges from dependencies to this hook + for (depClassName in meta.hookDependencies) { + if (depClassName in hooksByClassName) { + graph.computeIfAbsent(depClassName) { mutableListOf() }.add(meta.className) + } + } + } + + // Kahn's algorithm with priority queue for tie-breaking + val incomingEdges = mutableObject2IntMapOf() + for ((vertex, successors) in graph) { + if (vertex !in incomingEdges) { + incomingEdges[vertex] = 0 + } + for (successor in successors) { + incomingEdges.mergeInt(successor, 1, Int::plus) + } + } + + // Use a priority queue ordered by hook priority (lower priority value = higher priority) + val queue = PriorityQueue(compareBy { className -> + hooksByClassName[className]?.priority ?: Short.MAX_VALUE + }) + + incomingEdges.object2IntEntrySet().fastForEach { entry -> + val vertex = entry.key + val edges = entry.intValue + if (edges == 0) queue += vertex + } + + val result = mutableObjectListOf() + + while (queue.isNotEmpty()) { + val vertex = queue.poll() + hooksByClassName[vertex]?.let { result += it } + + for (successor in graph[vertex].orEmpty()) { + incomingEdges.mergeInt(successor, -1, Int::minus) + if (incomingEdges.getInt(successor) == 0) { + queue += successor + } + } + } + + if (result.size != incomingEdges.size) { + val chain = findCyclicDependency(graph, incomingEdges) + throw IllegalStateException( + "Circular hook dependency detected: ${chain.joinToString(" -> ")}" + ) + } + + return result + } + + private fun findCyclicDependency( + graph: Map>, + incomingEdges: Map + ): List { + // Find a node that still has incoming edges (part of cycle) + val cycleNode = incomingEdges.entries.firstOrNull { it.value > 0 }?.key + ?: return emptyList() + + // Trace back through dependencies to find the cycle + val visited = mutableSetOf() + val chain = mutableListOf() + var current = cycleNode + + while (current !in visited) { + visited.add(current) + chain.add(current) + // Find a successor that still has incoming edges (part of cycle) + current = graph[current]?.firstOrNull { (incomingEdges[it] ?: 0) > 0 } ?: break + } + + chain.add(current) + return chain + } + + private fun logMissingDependencies(owner: Any, hookClassName: String, missing: Map>) { + val logger = getLogger(owner) + + val lines = missing.entries + .sortedBy { it.key } + .joinToString(separator = System.lineSeparator()) { (type, ids) -> + val formattedIds = ids.toList().sorted().joinToString(", ") + " - $type: $formattedIds" + } + + logger.warn( + "Skipping hook $hookClassName due to missing dependencies:\n$lines" + ) + } + + suspend fun getHooks(owner: Any): List { + return hooksCache.get(owner) + } + + fun getHooksLoaded(owner: Any): List { + return hooksCache.underlying().asMap()[owner]?.getNow(emptyList()) ?: emptyList() + } + + suspend fun getAllHooks(): List { + return hooksCache.asMap().values.flatten().sorted() + } + + fun getAllHooksLoaded(): List { + return hooksCache.underlying().asMap().values.flatMap { it.getNow(emptyList()) }.sorted() + } + + abstract fun readHooksFileFromResources(owner: Any, fileName: String): InputStream? + abstract fun getClassloader(owner: Any): ClassLoader + abstract fun isPluginLoaded(pluginId: String): Boolean + abstract fun getLogger(owner: Any): ComponentLogger + + companion object { + val instance = requiredService() + fun get() = instance + } +} \ No newline at end of file diff --git a/surf-api-core/surf-api-core-server/src/main/kotlin/dev/slne/surf/surfapi/core/server/hook/HookServiceFallback.kt b/surf-api-core/surf-api-core-server/src/main/kotlin/dev/slne/surf/surfapi/core/server/hook/HookServiceFallback.kt new file mode 100644 index 00000000..9d975531 --- /dev/null +++ b/surf-api-core/surf-api-core-server/src/main/kotlin/dev/slne/surf/surfapi/core/server/hook/HookServiceFallback.kt @@ -0,0 +1,29 @@ +package dev.slne.surf.surfapi.core.server.hook + +import com.google.auto.service.AutoService +import net.kyori.adventure.text.logger.slf4j.ComponentLogger +import net.kyori.adventure.util.Services +import java.io.InputStream + +@AutoService(HookService::class) +class HookServiceFallback : HookService(), Services.Fallback { + override fun readHooksFileFromResources(owner: Any, fileName: String): InputStream? { + throwNotImplementedOnThisPlatform() + } + + override fun getClassloader(owner: Any): ClassLoader { + throwNotImplementedOnThisPlatform() + } + + override fun isPluginLoaded(pluginId: String): Boolean { + throwNotImplementedOnThisPlatform() + } + + override fun getLogger(owner: Any): ComponentLogger { + throwNotImplementedOnThisPlatform() + } + + private fun throwNotImplementedOnThisPlatform(): Nothing { + throw UnsupportedOperationException("This platform does not yet support hooks") + } +} \ No newline at end of file diff --git a/surf-api-core/surf-api-core-server/src/main/kotlin/dev/slne/surf/surfapi/core/server/impl/hook/SurfHookApiImpl.kt b/surf-api-core/surf-api-core-server/src/main/kotlin/dev/slne/surf/surfapi/core/server/impl/hook/SurfHookApiImpl.kt new file mode 100644 index 00000000..da704bdf --- /dev/null +++ b/surf-api-core/surf-api-core-server/src/main/kotlin/dev/slne/surf/surfapi/core/server/impl/hook/SurfHookApiImpl.kt @@ -0,0 +1,63 @@ +package dev.slne.surf.surfapi.core.server.impl.hook + +import com.google.auto.service.AutoService +import dev.slne.surf.surfapi.core.api.hook.SurfHookApi +import dev.slne.surf.surfapi.core.server.hook.HookService +import dev.slne.surf.surfapi.shared.api.hook.Hook + +@AutoService(SurfHookApi::class) +class SurfHookApiImpl : SurfHookApi { + override suspend fun bootstrap(owner: Any) { + for (hook in hooks(owner)) { + hook.bootstrap() + } + } + + override suspend fun load(owner: Any) { + for (hook in hooks(owner)) { + hook.load() + } + } + + override suspend fun enable(owner: Any) { + for (hook in hooks(owner)) { + hook.enable() + } + } + + override suspend fun disable(owner: Any) { + for (hook in hooks(owner).reversed()) { + hook.disable() + } + } + + override suspend fun hooksOfType( + owner: Any, + type: Class + ): List { + return hooks(owner).filterIsInstance(type) + } + + override fun hooksOfTypeLoaded( + owner: Any, + type: Class + ): List { + return hooksLoaded(owner).filterIsInstance(type) + } + + override suspend fun hooksOfType(type: Class): List { + return HookService.get().getAllHooks().filterIsInstance(type) + } + + override fun hooksOfTypeLoaded(type: Class): List { + return HookService.get().getAllHooksLoaded().filterIsInstance(type) + } + + override suspend fun hooks(owner: Any): List { + return HookService.get().getHooks(owner) + } + + override fun hooksLoaded(owner: Any): List { + return HookService.get().getHooksLoaded(owner) + } +} \ No newline at end of file diff --git a/surf-api-gradle-plugin/surf-api-processor/build.gradle.kts b/surf-api-gradle-plugin/surf-api-processor/build.gradle.kts index d296e209..1117bcc2 100644 --- a/surf-api-gradle-plugin/surf-api-processor/build.gradle.kts +++ b/surf-api-gradle-plugin/surf-api-processor/build.gradle.kts @@ -6,6 +6,7 @@ val snapshot = (findProperty("snapshot") as String).toBooleanStrict() plugins { kotlin("jvm") + kotlin("plugin.serialization") `publish-convention` } @@ -19,6 +20,7 @@ version = buildString { dependencies { implementation(libs.ksp.api) implementation(libs.auto.service.annotations) + api(project(":surf-api-shared:surf-api-shared-internal")) // https://mvnrepository.com/artifact/com.squareup/kotlinpoet implementation("com.squareup:kotlinpoet:2.2.0") diff --git a/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/autoservice/AutoServiceSymbolProcessor.kt b/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/autoservice/AutoServiceSymbolProcessor.kt index eeaaa37d..59d40c32 100644 --- a/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/autoservice/AutoServiceSymbolProcessor.kt +++ b/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/autoservice/AutoServiceSymbolProcessor.kt @@ -3,7 +3,6 @@ package dev.slne.surf.surfapi.processor.autoservice import com.google.auto.service.AutoService import com.google.devtools.ksp.closestClassDeclaration import com.google.devtools.ksp.getAllSuperTypes -import com.google.devtools.ksp.isLocal import com.google.devtools.ksp.processing.Dependencies import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessor @@ -12,7 +11,7 @@ import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSFile import com.google.devtools.ksp.symbol.KSType -import com.squareup.kotlinpoet.ClassName +import dev.slne.surf.surfapi.processor.util.toBinaryName import java.io.IOException class AutoServiceSymbolProcessor(environment: SymbolProcessorEnvironment) : SymbolProcessor { @@ -143,17 +142,6 @@ class AutoServiceSymbolProcessor(environment: SymbolProcessorEnvironment) : Symb } } - - private fun KSClassDeclaration.toClassName(): ClassName { - require(!isLocal()) { "Local/anonymous classes are not supported!" } - val pkg = packageName.asString() - val typesString = qualifiedName!!.asString().removePrefix("$pkg.") - val simpleNames = typesString.split(".") - return ClassName(pkg, simpleNames) - } - - private fun KSClassDeclaration.toBinaryName(): String = toClassName().reflectionName() - private fun checkImplementer( implementer: KSClassDeclaration, providerType: KSType, diff --git a/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/hook/HookSymbolProcessor.kt b/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/hook/HookSymbolProcessor.kt new file mode 100644 index 00000000..1a588250 --- /dev/null +++ b/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/hook/HookSymbolProcessor.kt @@ -0,0 +1,191 @@ +package dev.slne.surf.surfapi.processor.hook + +import com.google.devtools.ksp.closestClassDeclaration +import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSType +import dev.slne.surf.surfapi.processor.util.nameOf +import dev.slne.surf.surfapi.processor.util.toBinaryName +import dev.slne.surf.surfapi.shared.api.hook.HookMeta +import dev.slne.surf.surfapi.shared.api.hook.requirement.* +import dev.slne.surf.surfapi.shared.internal.hook.HooksConfig.HOOKS_FILE_NAME +import dev.slne.surf.surfapi.shared.internal.hook.HooksConfig.json +import dev.slne.surf.surfapi.shared.internal.hook.PluginHookMeta +import java.io.IOException + +class HookSymbolProcessor(environment: SymbolProcessorEnvironment) : SymbolProcessor { + companion object { + private val HOOK_ANNOTATION = nameOf() + private val DEPENDS_ON_CLASS_ANNOTATION = nameOf() + private val DEPENDS_ON_CLASS_NAME_ANNOTATION = nameOf() + private val DEPENDS_ON_ONE_PLUGIN_ANNOTATION = nameOf() + private val DEPENDS_ON_PLUGIN_ANNOTATION = nameOf() + private val DEPENDS_ON_HOOK_ANNOTATION = nameOf() + private val CONDITIONAL_ON_CUSTOM_ANNOTATION = nameOf() + } + + private val logger = environment.logger + private val codeGenerator = environment.codeGenerator + private val hooks = mutableSetOf() + + override fun process(resolver: Resolver): List { + val deferred = mutableListOf() + val hooksMetas = resolver.getSymbolsWithAnnotation(HOOK_ANNOTATION) + .filterIsInstance() + .mapNotNull { hookClass -> + var hasUnresolvedClassDependency = false + val hookMeta = hookClass.annotations.findAnnotation(HOOK_ANNOTATION) ?: run { + logger.error("@HookMeta annotation not found on element", hookClass) + return@mapNotNull null + } + + val priority = hookMeta.arguments.find { it.name?.asString() == "priority" }?.value as? Short ?: 0 + val dependsOnClass = hookClass.annotations.findAnnotations(DEPENDS_ON_CLASS_ANNOTATION) + .mapNotNull { annotation -> + val clazzValue = annotation.arguments.find { it.name?.asString() == "clazz" }?.value as? KSType + if (clazzValue == null) { + logger.error("DependsOnClass annotation must have 'clazz' parameter", annotation) + return@mapNotNull null + } + + if (clazzValue.isError) { + deferred += hookClass + hasUnresolvedClassDependency = true + return@mapNotNull null + } + + val closestClass = clazzValue.declaration.closestClassDeclaration() + if (closestClass == null) { + deferred += hookClass + hasUnresolvedClassDependency = true + return@mapNotNull null + } + closestClass.toBinaryName() + } + + if (hasUnresolvedClassDependency) { + return@mapNotNull null + } + + val dependsOnClassName = hookClass.annotations.findAnnotations(DEPENDS_ON_CLASS_NAME_ANNOTATION) + .mapNotNull { annotation -> + val classNameValue = + annotation.arguments.find { it.name?.asString() == "className" }?.value as? String + if (classNameValue == null) { + logger.error("@DependsOnClassName annotation must have 'className' parameter", annotation) + return@mapNotNull null + } + classNameValue + } + + val dependsOnOnePlugin = hookClass.annotations.findAnnotations(DEPENDS_ON_ONE_PLUGIN_ANNOTATION) + .mapNotNull { annotation -> + val argValue = annotation.arguments.find { it.name?.asString() == "pluginIds" }?.value + val pluginIds = when (argValue) { + is List<*> -> argValue.filterIsInstance() + is String -> listOf(argValue) + else -> emptyList() + } + + if (pluginIds.isEmpty()) { + logger.error("@DependsOnOnePlugin annotation must have 'pluginIds' parameter", annotation) + return@mapNotNull null + } + + pluginIds + } + + val dependsOnPlugin = hookClass.annotations.findAnnotations(DEPENDS_ON_PLUGIN_ANNOTATION) + .mapNotNull { annotation -> + val argValue = annotation.arguments.find { it.name?.asString() == "pluginId" }?.value + val pluginId = argValue as? String + if (pluginId == null) { + logger.error("@DependsOnPlugin annotation must have 'pluginId' parameter", annotation) + return@mapNotNull null + } + pluginId + } + + val dependsOnHook = hookClass.annotations.findAnnotations(DEPENDS_ON_HOOK_ANNOTATION) + .mapNotNull { annotation -> + val hookValue = annotation.arguments.find { it.name?.asString() == "hook" }?.value as? KSType + if (hookValue == null) { + logger.error("@DependsOnHook annotation must have 'hook' parameter", annotation) + return@mapNotNull null + } + + if (hookValue.isError) { + deferred += hookClass + hasUnresolvedClassDependency = true + return@mapNotNull null + } + + hookValue.declaration.closestClassDeclaration()?.toBinaryName() + } + + if (hasUnresolvedClassDependency) { + return@mapNotNull null + } + + val customConditions = hookClass.annotations.findAnnotations(CONDITIONAL_ON_CUSTOM_ANNOTATION) + .mapNotNull { annotation -> + val conditionValue = annotation.arguments.find { it.name?.asString() == "condition" }?.value as? KSType + conditionValue?.declaration?.closestClassDeclaration()?.toBinaryName() + } + + PluginHookMeta.Hook( + priority = priority, + className = hookClass.toBinaryName(), + classDependencies = dependsOnClass.toList() + dependsOnClassName.toList(), + pluginDependencies = dependsOnPlugin.toList(), + pluginOneDependencies = dependsOnOnePlugin.toList(), + hookDependencies = dependsOnHook.toList(), + customConditions = customConditions.toList() + ) + }.toList() + + hooks.addAll(hooksMetas) + return deferred + } + + override fun finish() { + generatePluginHookFile() + } + + + private fun generatePluginHookFile() { + if (hooks.isEmpty()) { + return + } + + val hookMeta = PluginHookMeta(hooks.toList()) + try { + codeGenerator.createNewFileByPath(Dependencies(aggregating = true), HOOKS_FILE_NAME, "").bufferedWriter() + .use { writer -> + val jsonString = json.encodeToString(hookMeta) + writer.write(jsonString) + } + + logger.info("Wrote Hooks to: $HOOKS_FILE_NAME") + } catch (e: IOException) { + logger.error("Unable to create $HOOKS_FILE_NAME, $e") + } + } + + private fun Sequence.findAnnotation(annotationClassName: String): KSAnnotation? { + return this.firstOrNull { + it.annotationType.resolve().declaration.qualifiedName?.asString() == annotationClassName + } + } + + private fun Sequence.findAnnotations(annotationClassName: String): Sequence { + return this.filter { + it.annotationType.resolve().declaration.qualifiedName?.asString() == annotationClassName + } + } +} diff --git a/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/hook/HookSymbolProcessorProvider.kt b/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/hook/HookSymbolProcessorProvider.kt new file mode 100644 index 00000000..a9ffa9bf --- /dev/null +++ b/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/hook/HookSymbolProcessorProvider.kt @@ -0,0 +1,11 @@ +package dev.slne.surf.surfapi.processor.hook + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +class HookSymbolProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return HookSymbolProcessor(environment) + } +} \ No newline at end of file diff --git a/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/util/util.kt b/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/util/util.kt new file mode 100644 index 00000000..bda62ba2 --- /dev/null +++ b/surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/util/util.kt @@ -0,0 +1,17 @@ +package dev.slne.surf.surfapi.processor.util + +import com.google.devtools.ksp.isLocal +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.squareup.kotlinpoet.ClassName + +fun KSClassDeclaration.toClassName(): ClassName { + require(!isLocal()) { "Local/anonymous classes are not supported!" } + val pkg = packageName.asString() + val typesString = qualifiedName!!.asString().removePrefix("$pkg.") + val simpleNames = typesString.split(".") + return ClassName(pkg, simpleNames) +} + +fun KSClassDeclaration.toBinaryName(): String = toClassName().reflectionName() + +inline fun nameOf(): String = T::class.java.name \ No newline at end of file diff --git a/surf-api-gradle-plugin/surf-api-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/surf-api-gradle-plugin/surf-api-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider index 82833035..6b2ab056 100644 --- a/surf-api-gradle-plugin/surf-api-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider +++ b/surf-api-gradle-plugin/surf-api-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -1 +1,2 @@ -dev.slne.surf.surfapi.processor.autoservice.AutoServiceSymbolProcessorProvider \ No newline at end of file +dev.slne.surf.surfapi.processor.autoservice.AutoServiceSymbolProcessorProvider +dev.slne.surf.surfapi.processor.hook.HookSymbolProcessorProvider \ No newline at end of file diff --git a/surf-api-hytale/surf-api-hytale-api/src/main/kotlin/dev/slne/surf/surfapi/hytale/api/coroutines/CoroutineSession.kt b/surf-api-hytale/surf-api-hytale-api/src/main/kotlin/dev/slne/surf/surfapi/hytale/api/coroutines/CoroutineSession.kt index fbe5c2fb..b2cfc61b 100644 --- a/surf-api-hytale/surf-api-hytale-api/src/main/kotlin/dev/slne/surf/surfapi/hytale/api/coroutines/CoroutineSession.kt +++ b/surf-api-hytale/surf-api-hytale-api/src/main/kotlin/dev/slne/surf/surfapi/hytale/api/coroutines/CoroutineSession.kt @@ -1,6 +1,6 @@ package dev.slne.surf.surfapi.hytale.api.coroutines -import dev.slne.surf.surfapi.core.api.util.InternalSurfApi +import dev.slne.surf.surfapi.shared.api.util.InternalSurfApi import kotlinx.coroutines.CoroutineScope import kotlin.coroutines.CoroutineContext diff --git a/surf-api-hytale/surf-api-hytale-api/src/main/kotlin/dev/slne/surf/surfapi/hytale/api/coroutines/HysCoroutine.kt b/surf-api-hytale/surf-api-hytale-api/src/main/kotlin/dev/slne/surf/surfapi/hytale/api/coroutines/HysCoroutine.kt index 5706e62e..69248f13 100644 --- a/surf-api-hytale/surf-api-hytale-api/src/main/kotlin/dev/slne/surf/surfapi/hytale/api/coroutines/HysCoroutine.kt +++ b/surf-api-hytale/surf-api-hytale-api/src/main/kotlin/dev/slne/surf/surfapi/hytale/api/coroutines/HysCoroutine.kt @@ -1,7 +1,7 @@ package dev.slne.surf.surfapi.hytale.api.coroutines import com.hypixel.hytale.server.core.plugin.JavaPlugin -import dev.slne.surf.surfapi.core.api.util.InternalSurfApi +import dev.slne.surf.surfapi.shared.api.util.InternalSurfApi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job diff --git a/surf-api-shared/build.gradle.kts b/surf-api-shared/build.gradle.kts new file mode 100644 index 00000000..4b36bc3a --- /dev/null +++ b/surf-api-shared/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + `core-convention` +} \ No newline at end of file diff --git a/surf-api-shared/surf-api-shared-internal/build.gradle.kts b/surf-api-shared/surf-api-shared-internal/build.gradle.kts new file mode 100644 index 00000000..08b86c6e --- /dev/null +++ b/surf-api-shared/surf-api-shared-internal/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `core-convention` +} + +dependencies { + api(project(":surf-api-shared:surf-api-shared-public")) +} \ No newline at end of file diff --git a/surf-api-shared/surf-api-shared-internal/src/main/kotlin/dev/slne/surf/surfapi/shared/internal/hook/HooksConfig.kt b/surf-api-shared/surf-api-shared-internal/src/main/kotlin/dev/slne/surf/surfapi/shared/internal/hook/HooksConfig.kt new file mode 100644 index 00000000..b78875e6 --- /dev/null +++ b/surf-api-shared/surf-api-shared-internal/src/main/kotlin/dev/slne/surf/surfapi/shared/internal/hook/HooksConfig.kt @@ -0,0 +1,13 @@ +package dev.slne.surf.surfapi.shared.internal.hook + +import kotlinx.serialization.json.Json + +object HooksConfig { + const val HOOKS_FILE_NAME = "surf-hooks.json" + val json = Json { + prettyPrint = true + encodeDefaults = false + ignoreUnknownKeys = true + prettyPrintIndent = " " + } +} \ No newline at end of file diff --git a/surf-api-shared/surf-api-shared-internal/src/main/kotlin/dev/slne/surf/surfapi/shared/internal/hook/PluginHookMeta.kt b/surf-api-shared/surf-api-shared-internal/src/main/kotlin/dev/slne/surf/surfapi/shared/internal/hook/PluginHookMeta.kt new file mode 100644 index 00000000..89255aab --- /dev/null +++ b/surf-api-shared/surf-api-shared-internal/src/main/kotlin/dev/slne/surf/surfapi/shared/internal/hook/PluginHookMeta.kt @@ -0,0 +1,22 @@ +package dev.slne.surf.surfapi.shared.internal.hook + +import kotlinx.serialization.Serializable + +@Serializable +data class PluginHookMeta(val hooks: List) { + + @Serializable + data class Hook( + val priority: Short, + val className: String, + val classDependencies: List = emptyList(), + val pluginDependencies: List = emptyList(), + val pluginOneDependencies: List> = emptyList(), + val hookDependencies: List = emptyList(), + val customConditions: List = emptyList(), + ) + + companion object { + fun empty() = PluginHookMeta(emptyList()) + } +} \ No newline at end of file diff --git a/surf-api-shared/surf-api-shared-public/api/surf-api-shared-public.api b/surf-api-shared/surf-api-shared-public/api/surf-api-shared-public.api new file mode 100644 index 00000000..2525d77d --- /dev/null +++ b/surf-api-shared/surf-api-shared-public/api/surf-api-shared-public.api @@ -0,0 +1,85 @@ +public abstract interface class dev/slne/surf/surfapi/shared/api/hook/Hook : java/lang/Comparable { + public abstract fun bootstrap (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun disable (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun enable (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun getPriority ()S + public abstract fun load (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/HookMeta : java/lang/annotation/Annotation { + public abstract fun priority ()S +} + +public abstract interface class dev/slne/surf/surfapi/shared/api/hook/condition/HookCondition { + public abstract fun evaluate (Ldev/slne/surf/surfapi/shared/api/hook/condition/HookConditionContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/slne/surf/surfapi/shared/api/hook/condition/HookConditionContext : java/lang/Record { + public fun (Ljava/lang/Object;Ljava/lang/Class;Lnet/kyori/adventure/text/logger/slf4j/ComponentLogger;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/Object;Ljava/lang/Class;Lnet/kyori/adventure/text/logger/slf4j/ComponentLogger;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Object; + public final fun component2 ()Ljava/lang/Class; + public final fun component3 ()Lnet/kyori/adventure/text/logger/slf4j/ComponentLogger; + public final fun component4 ()Ljava/util/Map; + public final fun copy (Ljava/lang/Object;Ljava/lang/Class;Lnet/kyori/adventure/text/logger/slf4j/ComponentLogger;Ljava/util/Map;)Ldev/slne/surf/surfapi/shared/api/hook/condition/HookConditionContext; + public static synthetic fun copy$default (Ldev/slne/surf/surfapi/shared/api/hook/condition/HookConditionContext;Ljava/lang/Object;Ljava/lang/Class;Lnet/kyori/adventure/text/logger/slf4j/ComponentLogger;Ljava/util/Map;ILjava/lang/Object;)Ldev/slne/surf/surfapi/shared/api/hook/condition/HookConditionContext; + public final fun environment ()Ljava/util/Map; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun hookClass ()Ljava/lang/Class; + public final fun logger ()Lnet/kyori/adventure/text/logger/slf4j/ComponentLogger; + public final fun owner ()Ljava/lang/Object; + public fun toString ()Ljava/lang/String; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/requirement/ConditionalOnCustom : java/lang/annotation/Annotation { + public abstract fun condition ()Ljava/lang/Class; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/requirement/ConditionalOnCustom$Container : java/lang/annotation/Annotation { + public abstract fun value ()[Ldev/slne/surf/surfapi/shared/api/hook/requirement/ConditionalOnCustom; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnClass : java/lang/annotation/Annotation { + public abstract fun clazz ()Ljava/lang/Class; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnClass$Container : java/lang/annotation/Annotation { + public abstract fun value ()[Ldev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnClass; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnClassName : java/lang/annotation/Annotation { + public abstract fun className ()Ljava/lang/String; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnClassName$Container : java/lang/annotation/Annotation { + public abstract fun value ()[Ldev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnClassName; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnHook : java/lang/annotation/Annotation { + public abstract fun hook ()Ljava/lang/Class; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnHook$Container : java/lang/annotation/Annotation { + public abstract fun value ()[Ldev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnHook; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnOnePlugin : java/lang/annotation/Annotation { + public abstract fun pluginIds ()[Ljava/lang/String; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnOnePlugin$Container : java/lang/annotation/Annotation { + public abstract fun value ()[Ldev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnOnePlugin; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnPlugin : java/lang/annotation/Annotation { + public abstract fun pluginId ()Ljava/lang/String; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnPlugin$Container : java/lang/annotation/Annotation { + public abstract fun value ()[Ldev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnPlugin; +} + +public abstract interface annotation class dev/slne/surf/surfapi/shared/api/util/InternalSurfApi : java/lang/annotation/Annotation { +} + diff --git a/surf-api-shared/surf-api-shared-public/build.gradle.kts b/surf-api-shared/surf-api-shared-public/build.gradle.kts new file mode 100644 index 00000000..df0fffb8 --- /dev/null +++ b/surf-api-shared/surf-api-shared-public/build.gradle.kts @@ -0,0 +1,26 @@ +@file:OptIn(ExperimentalAbiValidation::class) + +import org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation + +plugins { + `core-convention` +} + +kotlin { + abiValidation { + enabled = true + } +} + +dependencies { + compileOnlyApi(libs.adventure.api) + compileOnlyApi(libs.adventure.text.logger.slf4j) + compileOnlyApi(libs.adventure.text.minimessage) + compileOnlyApi(libs.adventure.serializer.gson) + compileOnlyApi(libs.adventure.serializer.legacy) + compileOnlyApi(libs.adventure.serializer.plain) + compileOnlyApi(libs.adventure.serializer.ansi) + + api(libs.kotlin.reflect) + api(libs.bundles.kotlin.serialization) +} \ No newline at end of file diff --git a/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/Hook.kt b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/Hook.kt new file mode 100644 index 00000000..6a87e4d3 --- /dev/null +++ b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/Hook.kt @@ -0,0 +1,9 @@ +package dev.slne.surf.surfapi.shared.api.hook + +interface Hook : Comparable { + val priority: Short + suspend fun bootstrap() + suspend fun load() + suspend fun enable() + suspend fun disable() +} \ No newline at end of file diff --git a/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/HookMeta.kt b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/HookMeta.kt new file mode 100644 index 00000000..b714a56f --- /dev/null +++ b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/HookMeta.kt @@ -0,0 +1,7 @@ +package dev.slne.surf.surfapi.shared.api.hook + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class HookMeta( + val priority: Short = 0 +) diff --git a/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/condition/HookCondition.kt b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/condition/HookCondition.kt new file mode 100644 index 00000000..24f6f4dc --- /dev/null +++ b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/condition/HookCondition.kt @@ -0,0 +1,5 @@ +package dev.slne.surf.surfapi.shared.api.hook.condition + +interface HookCondition { + suspend fun evaluate(context: HookConditionContext): Boolean +} \ No newline at end of file diff --git a/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/condition/HookConditionContext.kt b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/condition/HookConditionContext.kt new file mode 100644 index 00000000..f3ce80d4 --- /dev/null +++ b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/condition/HookConditionContext.kt @@ -0,0 +1,12 @@ +package dev.slne.surf.surfapi.shared.api.hook.condition + +import dev.slne.surf.surfapi.shared.api.hook.Hook +import net.kyori.adventure.text.logger.slf4j.ComponentLogger + +@JvmRecord +data class HookConditionContext( + val owner: Any, + val hookClass: Class, + val logger: ComponentLogger, + val environment: Map = emptyMap() +) \ No newline at end of file diff --git a/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/ConditionalOnCustom.kt b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/ConditionalOnCustom.kt new file mode 100644 index 00000000..2b249f71 --- /dev/null +++ b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/ConditionalOnCustom.kt @@ -0,0 +1,9 @@ +package dev.slne.surf.surfapi.shared.api.hook.requirement + +import dev.slne.surf.surfapi.shared.api.hook.condition.HookCondition +import kotlin.reflect.KClass + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@Repeatable +annotation class ConditionalOnCustom(val condition: KClass) \ No newline at end of file diff --git a/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnClass.kt b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnClass.kt new file mode 100644 index 00000000..ec11e6e7 --- /dev/null +++ b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnClass.kt @@ -0,0 +1,8 @@ +package dev.slne.surf.surfapi.shared.api.hook.requirement + +import kotlin.reflect.KClass + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@Repeatable +annotation class DependsOnClass(val clazz: KClass<*>) diff --git a/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnClassName.kt b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnClassName.kt new file mode 100644 index 00000000..9db89030 --- /dev/null +++ b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnClassName.kt @@ -0,0 +1,6 @@ +package dev.slne.surf.surfapi.shared.api.hook.requirement + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@Repeatable +annotation class DependsOnClassName(val className: String) \ No newline at end of file diff --git a/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnHook.kt b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnHook.kt new file mode 100644 index 00000000..1b984f7d --- /dev/null +++ b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnHook.kt @@ -0,0 +1,9 @@ +package dev.slne.surf.surfapi.shared.api.hook.requirement + +import dev.slne.surf.surfapi.shared.api.hook.Hook +import kotlin.reflect.KClass + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@Repeatable +annotation class DependsOnHook(val hook: KClass) diff --git a/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnOnePlugin.kt b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnOnePlugin.kt new file mode 100644 index 00000000..c4290759 --- /dev/null +++ b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnOnePlugin.kt @@ -0,0 +1,6 @@ +package dev.slne.surf.surfapi.shared.api.hook.requirement + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@Repeatable +annotation class DependsOnOnePlugin(val pluginIds: Array) diff --git a/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnPlugin.kt b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnPlugin.kt new file mode 100644 index 00000000..73966d91 --- /dev/null +++ b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/hook/requirement/DependsOnPlugin.kt @@ -0,0 +1,6 @@ +package dev.slne.surf.surfapi.shared.api.hook.requirement + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@Repeatable +annotation class DependsOnPlugin(val pluginId: String) diff --git a/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/util/internal-api.kt b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/util/internal-api.kt similarity index 77% rename from surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/util/internal-api.kt rename to surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/util/internal-api.kt index ba98c93c..27d4fe90 100644 --- a/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/util/internal-api.kt +++ b/surf-api-shared/surf-api-shared-public/src/main/kotlin/dev/slne/surf/surfapi/shared/api/util/internal-api.kt @@ -1,4 +1,4 @@ -package dev.slne.surf.surfapi.core.api.util +package dev.slne.surf.surfapi.shared.api.util @RequiresOptIn( "This API is internal and should not be used from outside the library", diff --git a/surf-api-velocity/surf-api-velocity-api/src/main/kotlin/dev/slne/surf/surfapi/velocity/api/command/executors/SuspendCommandExecutors.kt b/surf-api-velocity/surf-api-velocity-api/src/main/kotlin/dev/slne/surf/surfapi/velocity/api/command/executors/SuspendCommandExecutors.kt index c57ded33..cf5c73b3 100644 --- a/surf-api-velocity/surf-api-velocity-api/src/main/kotlin/dev/slne/surf/surfapi/velocity/api/command/executors/SuspendCommandExecutors.kt +++ b/surf-api-velocity/surf-api-velocity-api/src/main/kotlin/dev/slne/surf/surfapi/velocity/api/command/executors/SuspendCommandExecutors.kt @@ -14,7 +14,7 @@ import dev.jorel.commandapi.kotlindsl.consoleExecutor import dev.jorel.commandapi.kotlindsl.playerExecutor import dev.slne.surf.surfapi.core.api.messages.Colors import dev.slne.surf.surfapi.core.api.messages.adventure.text -import dev.slne.surf.surfapi.core.api.util.InternalSurfApi +import dev.slne.surf.surfapi.shared.api.util.InternalSurfApi import kotlinx.coroutines.* import net.kyori.adventure.text.logger.slf4j.ComponentLogger diff --git a/surf-api-velocity/surf-api-velocity-server/src/main/kotlin/dev/slne/surf/surfapi/velocity/server/hook/VelocityHookService.kt b/surf-api-velocity/surf-api-velocity-server/src/main/kotlin/dev/slne/surf/surfapi/velocity/server/hook/VelocityHookService.kt new file mode 100644 index 00000000..4078f0e8 --- /dev/null +++ b/surf-api-velocity/surf-api-velocity-server/src/main/kotlin/dev/slne/surf/surfapi/velocity/server/hook/VelocityHookService.kt @@ -0,0 +1,43 @@ +package dev.slne.surf.surfapi.velocity.server.hook + +import com.google.auto.service.AutoService +import dev.slne.surf.surfapi.core.server.hook.HookService +import dev.slne.surf.surfapi.velocity.server.velocityMain +import net.kyori.adventure.text.logger.slf4j.ComponentLogger +import java.io.IOException +import java.io.InputStream +import kotlin.jvm.optionals.getOrNull + +@AutoService(HookService::class) +class VelocityHookService : HookService() { + override fun readHooksFileFromResources(owner: Any, fileName: String): InputStream? { + return try { + val url = getClassloader(owner).getResource(fileName) ?: return null + val connection = url.openConnection() + connection.useCaches = false + connection.getInputStream() + } catch (_: IOException) { + null + } + } + + override fun getClassloader(owner: Any): ClassLoader { + return getInstanceFromOwner(owner).javaClass.classLoader + } + + override fun isPluginLoaded(pluginId: String): Boolean { + return velocityMain.server.pluginManager.isLoaded(pluginId) + } + + override fun getLogger(owner: Any): ComponentLogger { + return ComponentLogger.logger(getPluginContainerFromOwner(owner).description.id) + } + + private fun getPluginContainerFromOwner(owner: Any) = + velocityMain.server.pluginManager.ensurePluginContainer(owner) + + private fun getInstanceFromOwner(owner: Any): Any { + return getPluginContainerFromOwner(owner).instance.getOrNull() + ?: error("Failed to get instance from owner: $owner") + } +} \ No newline at end of file