From 8f3cc34740fcfe1572d23c8f1c1db1a309217b84 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Tue, 7 May 2024 20:26:04 +0200 Subject: Add @Subscribe annotation [no changelog] --- .gitignore | 1 + build.gradle.kts | 8 +- settings.gradle.kts | 11 +- .../firmament/events/subscription/Subscription.kt | 18 ++++ .../moe/nea/firmament/features/FeatureManager.kt | 18 ++++ .../moe/nea/firmament/features/FirmamentFeature.kt | 4 +- .../nea/firmament/features/inventory/PriceData.kt | 49 +++++---- .../firmament/features/inventory/SlotLocking.kt | 47 +++++---- symbols/build.gradle.kts | 19 ++++ symbols/src/main/kotlin/Subscribe.kt | 12 +++ .../kotlin/process/SubscribeAnnotationProcessor.kt | 116 +++++++++++++++++++++ 11 files changed, 254 insertions(+), 49 deletions(-) create mode 100644 src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt create mode 100644 symbols/build.gradle.kts create mode 100644 symbols/src/main/kotlin/Subscribe.kt create mode 100644 symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt diff --git a/.gitignore b/.gitignore index f58ab5f..9be7af4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /build/ /buildSrc/.gradle /buildSrc/build/ +/*/build # Ignore Gradle GUI config gradle-app.setting diff --git a/build.gradle.kts b/build.gradle.kts index 311da61..7d358dd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { java `maven-publish` + id("com.google.devtools.ksp") version "1.9.23-1.0.20" kotlin("jvm") version "1.9.23" kotlin("plugin.serialization") version "1.9.23" // id("com.bnorm.power.kotlin-power-assert") version "0.13.0" @@ -170,6 +171,9 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") + + implementation(project(":symbols")) + ksp(project(":symbols")) } tasks.test { @@ -193,8 +197,8 @@ loom { parseEnvFile(file(".env")).forEach { (t, u) -> environmentVariable(t, u) } - parseEnvFile(file(".properties")).forEach{ (t, u) -> - property(t,u) + parseEnvFile(file(".properties")).forEach { (t, u) -> + property(t, u) } vmArg("-ea") vmArg("-XX:+AllowEnhancedClassRedefinition") diff --git a/settings.gradle.kts b/settings.gradle.kts index bee19d4..79cf1a4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,10 @@ -// SPDX-FileCopyrightText: 2023 Linnea Gräf -// -// SPDX-License-Identifier: CC0-1.0 +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: CC0-1.0 + * SPDX-License-Identifier: GPL-3.0-or-later + */ pluginManagement { repositories { @@ -28,3 +32,4 @@ pluginManagement { rootProject.name = "Firmament" +include("symbols") diff --git a/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt b/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt new file mode 100644 index 0000000..1e7a6a8 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.events.subscription + +import moe.nea.firmament.events.FirmamentEvent +import moe.nea.firmament.events.FirmamentEventBus + +interface SubscriptionOwner + +data class Subscription( + val owner: SubscriptionOwner, + val invoke: (T) -> Unit, + val eventBus: FirmamentEventBus, +) diff --git a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt index a8daf63..669b3c9 100644 --- a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt +++ b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt @@ -10,7 +10,10 @@ package moe.nea.firmament.features import kotlinx.serialization.Serializable import kotlinx.serialization.serializer import moe.nea.firmament.Firmament +import moe.nea.firmament.annotations.generated.AllSubscriptions import moe.nea.firmament.events.FeaturesInitializedEvent +import moe.nea.firmament.events.FirmamentEvent +import moe.nea.firmament.events.subscription.Subscription import moe.nea.firmament.features.chat.AutoCompletions import moe.nea.firmament.features.chat.ChatLinks import moe.nea.firmament.features.chat.QuickCommands @@ -80,11 +83,26 @@ object FeatureManager : DataHolder(serializer(), "feature loadFeature(DebugView) } allFeatures.forEach { it.config } + subscribeEvents() FeaturesInitializedEvent.publish(FeaturesInitializedEvent(allFeatures.toList())) hasAutoloaded = true } } + private fun subscribeEvents() { + AllSubscriptions.provideSubscriptions { + subscribeSingleEvent(it) + } + } + + private fun subscribeSingleEvent(it: Subscription) { + if (it.owner in features.values) { // TODO: better check here, somehow. probably implement some interface method + it.eventBus.subscribe(false, it.invoke) // TODO: pass through receivesCancelled from the annotation + } else { + Firmament.logger.error("Ignoring event listener for ${it.eventBus} in ${it.owner}") + } + } + fun loadFeature(feature: FirmamentFeature) { synchronized(features) { if (feature.identifier in features) { diff --git a/src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt b/src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt index afd36a5..2416fec 100644 --- a/src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt +++ b/src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt @@ -1,14 +1,16 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ package moe.nea.firmament.features +import moe.nea.firmament.events.subscription.SubscriptionOwner import moe.nea.firmament.gui.config.ManagedConfig -interface FirmamentFeature { +interface FirmamentFeature : SubscriptionOwner { val identifier: String val defaultEnabled: Boolean get() = true diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/PriceData.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/PriceData.kt index aa8982d..b511611 100644 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/PriceData.kt +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/PriceData.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -7,6 +8,7 @@ package moe.nea.firmament.features.inventory import net.minecraft.text.Text +import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.ItemTooltipEvent import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.gui.config.ManagedConfig @@ -26,27 +28,32 @@ object PriceData : FirmamentFeature { override val config get() = TConfig override fun onLoad() { - ItemTooltipEvent.subscribe { - if (!TConfig.tooltipEnabled && !TConfig.enableKeybinding.isPressed()) { - return@subscribe - } - val sbId = it.stack.skyBlockId - val bazaarData = HypixelStaticData.bazaarData[sbId] - val lowestBin = HypixelStaticData.lowestBin[sbId] - if (bazaarData != null) { - it.lines.add(Text.literal("")) - it.lines.add( - Text.stringifiedTranslatable("firmament.tooltip.bazaar.sell-order", FirmFormatters.formatCurrency(bazaarData.quickStatus.sellPrice, 1)) - ) - it.lines.add( - Text.stringifiedTranslatable("firmament.tooltip.bazaar.buy-order", FirmFormatters.formatCurrency(bazaarData.quickStatus.buyPrice, 1)) - ) - } else if (lowestBin != null) { - it.lines.add(Text.literal("")) - it.lines.add( - Text.stringifiedTranslatable("firmament.tooltip.ah.lowestbin", FirmFormatters.formatCurrency(lowestBin, 1)) - ) - } + } + + @Subscribe + fun function(it: ItemTooltipEvent) { + if (!TConfig.tooltipEnabled && !TConfig.enableKeybinding.isPressed()) { + return + } + val sbId = it.stack.skyBlockId + val bazaarData = HypixelStaticData.bazaarData[sbId] + val lowestBin = HypixelStaticData.lowestBin[sbId] + if (bazaarData != null) { + it.lines.add(Text.literal("")) + it.lines.add( + Text.stringifiedTranslatable("firmament.tooltip.bazaar.sell-order", + FirmFormatters.formatCurrency(bazaarData.quickStatus.sellPrice, 1)) + ) + it.lines.add( + Text.stringifiedTranslatable("firmament.tooltip.bazaar.buy-order", + FirmFormatters.formatCurrency(bazaarData.quickStatus.buyPrice, 1)) + ) + } else if (lowestBin != null) { + it.lines.add(Text.literal("")) + it.lines.add( + Text.stringifiedTranslatable("firmament.tooltip.ah.lowestbin", + FirmFormatters.formatCurrency(lowestBin, 1)) + ) } } } diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt index 9bf2182..aee985c 100644 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt @@ -20,6 +20,7 @@ import net.minecraft.entity.player.PlayerInventory import net.minecraft.screen.GenericContainerScreenHandler import net.minecraft.screen.slot.SlotActionType import net.minecraft.util.Identifier +import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.HandledScreenKeyPressedEvent import moe.nea.firmament.events.IsSlotProtectedEvent import moe.nea.firmament.events.SlotRenderEvents @@ -166,29 +167,31 @@ object SlotLocking : FirmamentFeature { event.protectSilent() } } - SlotRenderEvents.After.subscribe { - val isSlotLocked = it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf()) - val isUUIDLocked = (it.slot.stack?.skyblockUUID) in (lockedUUIDs ?: setOf()) - if (isSlotLocked || isUUIDLocked) { - RenderSystem.disableDepthTest() - it.context.drawSprite( - it.slot.x, it.slot.y, 0, - 16, 16, - MC.guiAtlasManager.getSprite( - when { - isSlotLocked -> - (Identifier("firmament:slot_locked")) - - isUUIDLocked -> - (Identifier("firmament:uuid_locked")) - - else -> - error("unreachable") - } - ) + } + + @Subscribe + fun function(it: SlotRenderEvents.After) { + val isSlotLocked = it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf()) + val isUUIDLocked = (it.slot.stack?.skyblockUUID) in (lockedUUIDs ?: setOf()) + if (isSlotLocked || isUUIDLocked) { + RenderSystem.disableDepthTest() + it.context.drawSprite( + it.slot.x, it.slot.y, 0, + 16, 16, + MC.guiAtlasManager.getSprite( + when { + isSlotLocked -> + (Identifier("firmament:slot_locked")) + + isUUIDLocked -> + (Identifier("firmament:uuid_locked")) + + else -> + error("unreachable") + } ) - RenderSystem.enableDepthTest() - } + ) + RenderSystem.enableDepthTest() } } } diff --git a/symbols/build.gradle.kts b/symbols/build.gradle.kts new file mode 100644 index 0000000..fad3221 --- /dev/null +++ b/symbols/build.gradle.kts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +plugins { + kotlin("jvm") + id("com.google.devtools.ksp") +} + +repositories { + mavenCentral() +} +dependencies { + ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0") + implementation("com.google.auto.service:auto-service-annotations:1.1.1") + implementation("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20") +} diff --git a/symbols/src/main/kotlin/Subscribe.kt b/symbols/src/main/kotlin/Subscribe.kt new file mode 100644 index 0000000..e7a7bf4 --- /dev/null +++ b/symbols/src/main/kotlin/Subscribe.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.annotations + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.SOURCE) +annotation class Subscribe + diff --git a/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt b/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt new file mode 100644 index 0000000..be817ce --- /dev/null +++ b/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.annotations.process + +import com.google.auto.service.AutoService +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.KSPLogger +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.processing.SymbolProcessorProvider +import com.google.devtools.ksp.symbol.ClassKind +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSType +import com.google.devtools.ksp.symbol.Nullability +import com.google.devtools.ksp.validate +import java.text.SimpleDateFormat +import java.util.Date +import moe.nea.firmament.annotations.Subscribe + +class SubscribeAnnotationProcessor( + val logger: KSPLogger, + val codeGenerator: CodeGenerator, +) : SymbolProcessor { + override fun finish() { + val dependencies = Dependencies( + aggregating = true, + *subscriptions.mapTo(mutableSetOf()) { it.parent.containingFile!! }.toTypedArray()) + val subscriptionsFile = + codeGenerator + .createNewFile(dependencies, "moe.nea.firmament.annotations.generated", "AllSubscriptions") + .bufferedWriter() + subscriptionsFile.apply { + appendLine("// This file is @generated by SubscribeAnnotationProcessor at ${SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format( + Date())}") + appendLine("// Do not edit") + appendLine("package moe.nea.firmament.annotations.generated") + appendLine() + appendLine("import moe.nea.firmament.events.subscription.*") + appendLine() + appendLine("object AllSubscriptions {") + appendLine(" fun provideSubscriptions(addSubscription: (Subscription<*>) -> Unit) {") + for (subscription in subscriptions) { + val owner = subscription.parent.qualifiedName!!.asString() + val method = subscription.child.simpleName.asString() + val type = subscription.type.declaration.qualifiedName!!.asString() + appendLine(" addSubscription(Subscription<$type>(") + appendLine(" ${owner},") + appendLine(" ${owner}::${method},") + appendLine(" ${type}))") + } + appendLine(" }") + appendLine("}") + } + subscriptionsFile.close() + } + + data class Subscription( + val parent: KSClassDeclaration, + val child: KSFunctionDeclaration, + val type: KSType, + ) + + val subscriptions = mutableListOf() + + fun processCandidates(list: List) { + for (element in list) { + if (element !is KSFunctionDeclaration) { + logger.error("@Subscribe annotation on a not-function", element) + continue + } + if (element.isAbstract) { + logger.error("@Subscribe annotation on an abstract function", element) + continue + } + val parent = element.parentDeclaration + if (parent !is KSClassDeclaration || parent.isCompanionObject || parent.classKind != ClassKind.OBJECT) { + logger.error("@Subscribe on a non-object", element) + continue + } + val param = element.parameters.singleOrNull() + if (param == null) { + logger.error("@Subscribe annotated functions need to take exactly one parameter", element) + continue + } + val type = param.type.resolve() + if (type.nullability != Nullability.NOT_NULL) { + logger.error("@Subscribe annotated functions cannot take a nullable event", element) + continue + } + subscriptions.add(Subscription(parent, element, type)) + } + } + + override fun process(resolver: Resolver): List { + val candidates = resolver.getSymbolsWithAnnotation(Subscribe::class.qualifiedName!!).toList() + val valid = candidates.filter { it.validate() } + val invalid = candidates.filter { !it.validate() } + processCandidates(valid) + return invalid + } +} + +@AutoService(SymbolProcessorProvider::class) +class SubscribeAnnotationProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return SubscribeAnnotationProcessor(environment.logger, environment.codeGenerator) + } +} -- cgit