Skip to content

Commit

Permalink
Backend: Event Predicates (#3417)
Browse files Browse the repository at this point in the history
Co-authored-by: Empa <[email protected]>
Co-authored-by: hannibal2 <[email protected]>
  • Loading branch information
3 people authored Feb 13, 2025
1 parent ff4a460 commit 9048725
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 62 deletions.
44 changes: 3 additions & 41 deletions src/main/java/at/hannibal2/skyhanni/api/event/EventHandler.kt
Original file line number Diff line number Diff line change
@@ -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<T : SkyHanniEvent> private constructor(
val name: String,
Expand All @@ -23,25 +17,10 @@ class EventHandler<T : SkyHanniEvent> private constructor(

constructor(event: Class<T>, listeners: List<EventListeners.Listener>) : 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<Int>() {
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
Expand All @@ -50,9 +29,8 @@ class EventHandler<T : SkyHanniEvent> 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) {
Expand All @@ -67,7 +45,6 @@ class EventHandler<T : SkyHanniEvent> private constructor(
}
if (event.isCancelled && !canReceiveCancelled) break
}
eventHandlerDepth--

if (errors > 3) {
val hiddenErrors = errors - 3
Expand All @@ -79,19 +56,4 @@ class EventHandler<T : SkyHanniEvent> 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
}
}
61 changes: 54 additions & 7 deletions src/main/java/at/hannibal2/skyhanni/api/event/EventListeners.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
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
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<Listener> = mutableListOf()
Expand Down Expand Up @@ -68,15 +74,56 @@ class EventListeners private constructor(val name: String, private val isGeneric
class Listener(
val name: String,
val invoker: Consumer<Any>,
val options: HandleEvent,
val generic: Class<*>?,
options: HandleEvent,
private val generic: Class<*>?,
extraPredicates: List<EventPredicate> = listOf(),
) {
val onlyOnIslandTypes: Set<IslandType> = getIslands(options)
val priority: Int = options.priority
val receiveCancelled: Boolean = options.receiveCancelled

@Suppress("JoinDeclarationAndAssignment")
private val cachedPredicates: List<EventPredicate>
private var lastTick = -1
private var cachedPredicateValue = false

private val predicates: List<EventPredicate>

companion object {
private fun getIslands(options: HandleEvent): Set<IslandType> =
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)
}
}
}

}
3 changes: 1 addition & 2 deletions src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String, Any?>): Nothing {
val exception = IllegalStateException(message.removeColor())
println("silent SkyHanni error:")
Expand All @@ -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,
Expand Down
6 changes: 1 addition & 5 deletions src/main/java/at/hannibal2/skyhanni/utils/ConfigUtils.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
}

/**
Expand Down

0 comments on commit 9048725

Please sign in to comment.