From b8adb961f50d17c02dbccbbb00376a2f728c819b Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Thu, 18 Jan 2024 21:39:21 +0100 Subject: Add hotswap agent support (#910) Added hotswap detection and reloading all listeners on hotswap. #910 --- build.gradle.kts | 1 + gradle/libs.versions.toml | 1 + src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt | 3 ++ .../skyhanni/test/hotswap/HotswapSupport.kt | 22 +++++++++ .../skyhanni/test/hotswap/HotswapSupportHandle.kt | 6 +++ .../skyhanni/test/hotswap/HotswapSupportImpl.kt | 57 ++++++++++++++++++++++ src/main/resources/hotswap-agent.properties | 1 + 7 files changed, 91 insertions(+) create mode 100644 src/main/java/at/hannibal2/skyhanni/test/hotswap/HotswapSupport.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/test/hotswap/HotswapSupportHandle.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/test/hotswap/HotswapSupportImpl.kt create mode 100644 src/main/resources/hotswap-agent.properties diff --git a/build.gradle.kts b/build.gradle.kts index 3573835f3..e2d7e04fa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -111,6 +111,7 @@ dependencies { shadowModImpl(libs.moulconfig) shadowImpl(libs.libautoupdate) shadowImpl("org.jetbrains.kotlin:kotlin-reflect:1.9.0") + implementation(libs.hotswapagentforge) // testImplementation(kotlin("test")) testImplementation("com.github.NotEnoughUpdates:NotEnoughUpdates:v2.1.1-pre4:all") { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ce1a16908..99aad1330 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,3 +9,4 @@ moulconfig = { module = "org.notenoughupdates.moulconfig:legacy", version.ref = libautoupdate = { module = "moe.nea:libautoupdate", version.ref = "libautoupdate" } headlessLwjgl = { module = "com.github.3arthqu4ke.HeadlessMc:headlessmc-lwjgl", version.ref = "headlessLwjgl" } jbAnnotations = { module = "org.jetbrains:annotations", version.ref = "jbAnnotations" } +hotswapagentforge = { module = "moe.nea:hotswapagent-forge", version = "1.0.1" } diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index f7c257706..addfbf1f0 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -341,6 +341,7 @@ import at.hannibal2.skyhanni.test.TestExportTools import at.hannibal2.skyhanni.test.TestShowSlotNumber import at.hannibal2.skyhanni.test.WorldEdit import at.hannibal2.skyhanni.test.command.CopyNearbyParticlesCommand +import at.hannibal2.skyhanni.test.hotswap.HotswapSupport import at.hannibal2.skyhanni.utils.EntityOutlineRenderer import at.hannibal2.skyhanni.utils.KeyboardManager import at.hannibal2.skyhanni.utils.LorenzUtils @@ -378,6 +379,8 @@ class SkyHanniMod { fun preInit(event: FMLPreInitializationEvent?) { checkIfNeuIsLoaded() + HotswapSupport.load() + // data loadModule(this) loadModule(ChatManager) diff --git a/src/main/java/at/hannibal2/skyhanni/test/hotswap/HotswapSupport.kt b/src/main/java/at/hannibal2/skyhanni/test/hotswap/HotswapSupport.kt new file mode 100644 index 000000000..87acab62d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/test/hotswap/HotswapSupport.kt @@ -0,0 +1,22 @@ +package at.hannibal2.skyhanni.test.hotswap + +import java.util.function.Supplier + +object HotswapSupport { + private val isForgeSidePresent = + runCatching { Class.forName("moe.nea.hotswapagentforge.forge.HotswapEvent") }.isSuccess + private val obj = if (isForgeSidePresent) { + Supplier { HotswapSupportImpl() } + } else { + Supplier { null } + }.get() + + fun isLoaded(): Boolean { + return obj?.isLoaded() ?: false + } + + fun load() { + obj?.load() + } +} + diff --git a/src/main/java/at/hannibal2/skyhanni/test/hotswap/HotswapSupportHandle.kt b/src/main/java/at/hannibal2/skyhanni/test/hotswap/HotswapSupportHandle.kt new file mode 100644 index 000000000..c006753a0 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/test/hotswap/HotswapSupportHandle.kt @@ -0,0 +1,6 @@ +package at.hannibal2.skyhanni.test.hotswap + +interface HotswapSupportHandle { + fun load() + fun isLoaded(): Boolean +} diff --git a/src/main/java/at/hannibal2/skyhanni/test/hotswap/HotswapSupportImpl.kt b/src/main/java/at/hannibal2/skyhanni/test/hotswap/HotswapSupportImpl.kt new file mode 100644 index 000000000..df74eedbb --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/test/hotswap/HotswapSupportImpl.kt @@ -0,0 +1,57 @@ +package at.hannibal2.skyhanni.test.hotswap + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.makeAccessible +import at.hannibal2.skyhanni.utils.LorenzUtils.removeFinal +import moe.nea.hotswapagentforge.forge.ClassDefinitionEvent +import moe.nea.hotswapagentforge.forge.HotswapEvent +import moe.nea.hotswapagentforge.forge.HotswapFinishedEvent +import net.minecraft.client.Minecraft +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class HotswapSupportImpl : HotswapSupportHandle { + override fun load() { + MinecraftForge.EVENT_BUS.register(this) + println("Hotswap Client in Skyhanni loaded") + } + + @SubscribeEvent + fun onHotswapClass(event: ClassDefinitionEvent.Redefinition) { + val instance = SkyHanniMod.modules.find { it.javaClass.name == event.fullyQualifiedName } + if (instance == null) return + val primaryConstructor = runCatching { instance.javaClass.getDeclaredConstructor() }.getOrNull() + Minecraft.getMinecraft().addScheduledTask(Runnable { + LorenzUtils.chat("Refreshing event subscriptions for module $instance!") + MinecraftForge.EVENT_BUS.unregister(instance) + if (primaryConstructor == null) { + MinecraftForge.EVENT_BUS.register(instance) + } else { + SkyHanniMod.modules.remove(instance) + primaryConstructor.isAccessible = true + val newInstance = primaryConstructor.newInstance() + LorenzUtils.chat("Reconstructing $instance -> $newInstance!") + val instanceField = runCatching { instance.javaClass.getDeclaredField("INSTANCE") }.getOrNull() + ?.takeIf { it.type == instance.javaClass } + ?.makeAccessible() + ?.removeFinal() + if (instanceField != null) { + LorenzUtils.chat("Reinjected static instance $newInstance!") + instanceField.set(null, newInstance) + } + SkyHanniMod.modules.add(newInstance) + MinecraftForge.EVENT_BUS.register(newInstance) + } + }) + } + + @SubscribeEvent + fun onHotswapDetected(event: HotswapFinishedEvent) { + LorenzUtils.chat("Hotswap finished!") + } + + override fun isLoaded(): Boolean { + return HotswapEvent.isReady() + } +} diff --git a/src/main/resources/hotswap-agent.properties b/src/main/resources/hotswap-agent.properties new file mode 100644 index 000000000..230a7d99a --- /dev/null +++ b/src/main/resources/hotswap-agent.properties @@ -0,0 +1 @@ +pluginPackages=moe.nea.hotswapagentforge.plugin -- cgit