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

Open
wants to merge 6 commits into
base: beta
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 the config")
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
Loading