From 9048725d995a3ef28390e13929db48d15f158b1d Mon Sep 17 00:00:00 2001 From: Empa <42304516+ItsEmpa@users.noreply.github.com> Date: Thu, 13 Feb 2025 03:37:00 +0100 Subject: [PATCH] Backend: Event Predicates (#3417) Co-authored-by: Empa Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- .../skyhanni/api/event/EventHandler.kt | 44 +------------ .../skyhanni/api/event/EventListeners.kt | 61 ++++++++++++++++--- .../skyhanni/config/ConfigManager.kt | 3 +- .../skyhanni/test/command/ErrorManager.kt | 9 ++- .../hannibal2/skyhanni/utils/ConfigUtils.kt | 6 +- .../utils/repopatterns/RepoPatternManager.kt | 9 +-- 6 files changed, 70 insertions(+), 62 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/api/event/EventHandler.kt b/src/main/java/at/hannibal2/skyhanni/api/event/EventHandler.kt index 43f0879642fd..ce5cc996fda1 100644 --- a/src/main/java/at/hannibal2/skyhanni/api/event/EventHandler.kt +++ b/src/main/java/at/hannibal2/skyhanni/api/event/EventHandler.kt @@ -1,16 +1,10 @@ package at.hannibal2.skyhanni.api.event import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.data.IslandType -import at.hannibal2.skyhanni.mixins.hooks.getValue -import at.hannibal2.skyhanni.mixins.hooks.setValue import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.ChatUtils -import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.inAnyIsland import at.hannibal2.skyhanni.utils.StringUtils import at.hannibal2.skyhanni.utils.chat.Text -import at.hannibal2.skyhanni.utils.system.PlatformUtils class EventHandler private constructor( val name: String, @@ -23,25 +17,10 @@ class EventHandler private constructor( constructor(event: Class, listeners: List) : this( (event.name.split(".").lastOrNull() ?: event.name).replace("$", "."), - listeners.sortedBy { it.options.priority }.toList(), - listeners.any { it.options.receiveCancelled }, + listeners.sortedBy { it.priority }.toList(), + listeners.any { it.receiveCancelled }, ) - companion object { - private var eventHandlerDepth by object : ThreadLocal() { - override fun initialValue(): Int { - return 0 - } - } - - /** - * Returns true if the current thread is in an event handler. This is because the event handler catches exceptions which means - * that we are free to throw exceptions in the event handler without crashing the game. - * We also return true if we are in a dev environment to alert the developer of any errors effectively. - */ - val isInEventHandler get() = eventHandlerDepth > 0 || PlatformUtils.isDevEnvironment - } - fun post(event: T, onError: ((Throwable) -> Unit)? = null): Boolean { invokeCount++ if (this.listeners.isEmpty()) return false @@ -50,9 +29,8 @@ class EventHandler private constructor( var errors = 0 - eventHandlerDepth++ for (listener in listeners) { - if (!shouldInvoke(event, listener)) continue + if (!listener.shouldInvoke(event)) continue try { listener.invoker.accept(event) } catch (throwable: Throwable) { @@ -67,7 +45,6 @@ class EventHandler private constructor( } if (event.isCancelled && !canReceiveCancelled) break } - eventHandlerDepth-- if (errors > 3) { val hiddenErrors = errors - 3 @@ -79,19 +56,4 @@ class EventHandler private constructor( } return event.isCancelled } - - private fun shouldInvoke(event: SkyHanniEvent, listener: EventListeners.Listener): Boolean { - if (SkyHanniEvents.isDisabledInvoker(listener.name)) return false - if (listener.options.onlyOnSkyblock && !LorenzUtils.inSkyBlock) return false - if (IslandType.ANY !in listener.onlyOnIslandTypes && !inAnyIsland(listener.onlyOnIslandTypes)) return false - if (event.isCancelled && !listener.options.receiveCancelled) return false - if ( - event is GenericSkyHanniEvent<*> && - listener.generic != null && - !listener.generic.isAssignableFrom(event.type) - ) { - return false - } - return true - } } diff --git a/src/main/java/at/hannibal2/skyhanni/api/event/EventListeners.kt b/src/main/java/at/hannibal2/skyhanni/api/event/EventListeners.kt index 62a22a7d0d64..22c9496e9a0b 100644 --- a/src/main/java/at/hannibal2/skyhanni/api/event/EventListeners.kt +++ b/src/main/java/at/hannibal2/skyhanni/api/event/EventListeners.kt @@ -1,6 +1,10 @@ package at.hannibal2.skyhanni.api.event import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.data.MinecraftData +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.inAnyIsland +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland import at.hannibal2.skyhanni.utils.ReflectionUtils import java.lang.invoke.LambdaMetafactory import java.lang.invoke.MethodHandles @@ -8,6 +12,8 @@ import java.lang.invoke.MethodType import java.lang.reflect.Method import java.util.function.Consumer +typealias EventPredicate = (event: SkyHanniEvent) -> Boolean + class EventListeners private constructor(val name: String, private val isGeneric: Boolean) { private val listeners: MutableList = mutableListOf() @@ -68,15 +74,56 @@ class EventListeners private constructor(val name: String, private val isGeneric class Listener( val name: String, val invoker: Consumer, - val options: HandleEvent, - val generic: Class<*>?, + options: HandleEvent, + private val generic: Class<*>?, + extraPredicates: List = listOf(), ) { - val onlyOnIslandTypes: Set = getIslands(options) + val priority: Int = options.priority + val receiveCancelled: Boolean = options.receiveCancelled + + @Suppress("JoinDeclarationAndAssignment") + private val cachedPredicates: List + private var lastTick = -1 + private var cachedPredicateValue = false + + private val predicates: List - companion object { - private fun getIslands(options: HandleEvent): Set = - if (options.onlyOnIslands.isEmpty()) setOf(options.onlyOnIsland) - else options.onlyOnIslands.toSet() + fun shouldInvoke(event: SkyHanniEvent): Boolean { + if (SkyHanniEvents.isDisabledInvoker(name)) return false + if (lastTick != MinecraftData.totalTicks) { + cachedPredicateValue = cachedPredicates.all { it(event) } + lastTick = MinecraftData.totalTicks + } + return cachedPredicateValue && predicates.all { it(event) } + } + + init { + cachedPredicates = buildList { + if (options.onlyOnSkyblock) add { _ -> LorenzUtils.inSkyBlock } + + if (options.onlyOnIsland != IslandType.ANY) { + val island = options.onlyOnIsland + add { _ -> island.isInIsland() } + } + + if (options.onlyOnIslands.isNotEmpty()) { + val set = options.onlyOnIslands.toSet() + add { _ -> inAnyIsland(set) } + } + } + // These predicates cant be cached since they depend on info about the actual event + predicates = buildList { + if (receiveCancelled) add { event -> !event.isCancelled } + + if (generic != null) { + add { event -> + event is GenericSkyHanniEvent<*> && generic.isAssignableFrom(event.type) + } + } + // Makes it possible to be able to add more predicates from other sources, such as other annotations + addAll(extraPredicates) + } } } + } diff --git a/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt b/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt index a70d18595f06..200e4858fe31 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt @@ -1,7 +1,6 @@ package at.hannibal2.skyhanni.config import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.api.event.EventHandler import at.hannibal2.skyhanni.config.core.config.Position import at.hannibal2.skyhanni.config.core.config.PositionList import at.hannibal2.skyhanni.data.jsonobjects.local.FriendsJson @@ -99,7 +98,7 @@ class ConfigManager { try { findPositionLinks(features, mutableSetOf()) } catch (e: Exception) { - if (EventHandler.isInEventHandler) throw e + ErrorManager.crashInDevEnv("Couldn't load config links") { e } } } diff --git a/src/main/java/at/hannibal2/skyhanni/test/command/ErrorManager.kt b/src/main/java/at/hannibal2/skyhanni/test/command/ErrorManager.kt index 83bd63dda61d..8b24d7d49551 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/command/ErrorManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/command/ErrorManager.kt @@ -12,7 +12,9 @@ import at.hannibal2.skyhanni.utils.OSUtils import at.hannibal2.skyhanni.utils.StringUtils import at.hannibal2.skyhanni.utils.StringUtils.removeColor import at.hannibal2.skyhanni.utils.TimeLimitedSet +import at.hannibal2.skyhanni.utils.system.PlatformUtils import net.minecraft.client.Minecraft +import net.minecraft.crash.CrashReport import kotlin.time.Duration.Companion.minutes @SkyHanniModule @@ -64,7 +66,7 @@ object ErrorManager { cache.clear() } - // throw a error, best to not use it if not absolutely necessary + // throw an error, best to not use it if not absolutely necessary fun skyHanniError(message: String, vararg extraData: Pair): Nothing { val exception = IllegalStateException(message.removeColor()) println("silent SkyHanni error:") @@ -89,6 +91,11 @@ object ErrorManager { ) } + inline fun crashInDevEnv(reason: String, t: (String) -> Throwable = { RuntimeException(it) }) { + if (!PlatformUtils.isDevEnvironment) return + Minecraft.getMinecraft().crashed(CrashReport("SkyHanni - $reason", t(reason))) + } + // just log for debug cases fun logErrorStateWithData( userMessage: String, diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ConfigUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ConfigUtils.kt index b26260e5a8bf..aefad8547957 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/ConfigUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/ConfigUtils.kt @@ -1,7 +1,6 @@ package at.hannibal2.skyhanni.utils import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.api.event.EventHandler import at.hannibal2.skyhanni.config.ConfigGuiManager import at.hannibal2.skyhanni.config.HasLegacyId import at.hannibal2.skyhanni.test.command.ErrorManager @@ -85,10 +84,7 @@ object ConfigUtils { fun KMutableProperty0<*>.jumpToEditor() { if (tryJumpToEditor(ConfigGuiManager.getEditorInstance())) return - // TODO create utils function "crashIfInDevEnv" - if (EventHandler.isInEventHandler) { - throw Error("can not jump to editor $name") - } + ErrorManager.crashInDevEnv("Can not open config $name") ErrorManager.logErrorStateWithData( "Can not open the config", "error while trying to jump to an editor element", diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt index 273917bda549..1bf99029ff91 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt @@ -1,7 +1,6 @@ package at.hannibal2.skyhanni.utils.repopatterns import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.api.event.EventHandler import at.hannibal2.skyhanni.api.event.HandleEvent import at.hannibal2.skyhanni.config.ConfigManager import at.hannibal2.skyhanni.config.features.dev.RepoPatternConfig @@ -79,12 +78,10 @@ object RepoPatternManager { private val logger = LogManager.getLogger("SkyHanni") /** - * Crash if in a development environment, or if inside a guarded event handler. + * Crash if in a development environment. */ - fun crash(reason: String) { - if (EventHandler.isInEventHandler) { - throw RuntimeException(reason) - } + private fun crash(reason: String) { + if (PlatformUtils.isDevEnvironment) throw RuntimeException(reason) } /**