Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend: Event Predicates #3417

Merged
merged 8 commits into from
Feb 13, 2025
Merged
41 changes: 3 additions & 38 deletions src/main/java/at/hannibal2/skyhanni/api/event/EventHandler.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
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

Check warning on line 10 in src/main/java/at/hannibal2/skyhanni/api/event/EventHandler.kt

View workflow job for this annotation

GitHub Actions / Run detekt

detekt.formatting.NoUnusedImports

Unused import

class EventHandler<T : SkyHanniEvent> private constructor(
val name: String,
Expand All @@ -23,25 +20,10 @@

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 +32,8 @@

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 +48,6 @@
}
if (event.isCancelled && !canReceiveCancelled) break
}
eventHandlerDepth--

if (errors > 3) {
val hiddenErrors = errors - 3
Expand All @@ -79,19 +59,4 @@
}
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)
}
}
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package at.hannibal2.skyhanni.config

import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.api.event.EventHandler

Check warning on line 4 in src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt

View workflow job for this annotation

GitHub Actions / Run detekt

detekt.formatting.NoUnusedImports

Unused import
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 +99,7 @@
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: () -> Throwable = { RuntimeException("SkyHanni crash") }) {
if (!PlatformUtils.isDevEnvironment) return
Minecraft.getMinecraft().crashed(CrashReport("SkyHanni - $reason", t()))
}

// just log for debug cases
fun logErrorStateWithData(
userMessage: String,
Expand Down
4 changes: 1 addition & 3 deletions src/main/java/at/hannibal2/skyhanni/utils/ConfigUtils.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package at.hannibal2.skyhanni.utils

import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.api.event.EventHandler

Check warning on line 4 in src/main/java/at/hannibal2/skyhanni/utils/ConfigUtils.kt

View workflow job for this annotation

GitHub Actions / Run detekt

detekt.formatting.NoUnusedImports

Unused import
import at.hannibal2.skyhanni.config.ConfigGuiManager
import at.hannibal2.skyhanni.config.HasLegacyId
import at.hannibal2.skyhanni.test.command.ErrorManager
Expand Down Expand Up @@ -86,9 +86,7 @@
if (tryJumpToEditor(ConfigGuiManager.getEditorInstance())) return

// TODO create utils function "crashIfInDevEnv"
ItsEmpa marked this conversation as resolved.
Show resolved Hide resolved
if (EventHandler.isInEventHandler) {
throw Error("can not jump to editor $name")
}
ErrorManager.crashInDevEnv("Can not open the config")
hannibal002 marked this conversation as resolved.
Show resolved Hide resolved
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,7 @@
package at.hannibal2.skyhanni.utils.repopatterns

import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.api.event.EventHandler

Check warning on line 4 in src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt

View workflow job for this annotation

GitHub Actions / Run detekt

detekt.formatting.NoUnusedImports

Unused import
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,13 +79,9 @@
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) = ErrorManager.crashInDevEnv(reason) { RuntimeException(reason) }

/**
* Check that the [owner] has exclusive right to the specified [key], and locks out other code parts from ever
Expand Down
Loading