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

Improve event handling #457

Merged
merged 2 commits into from
Sep 11, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package me.partlysanestudios.partlysaneskies

import com.google.gson.JsonObject
import com.google.gson.JsonParser
import me.partlysanestudios.partlysaneskies.api.events.PSSEvents
import me.partlysanestudios.partlysaneskies.config.Keybinds
import me.partlysanestudios.partlysaneskies.config.OneConfigScreen
import me.partlysanestudios.partlysaneskies.config.psconfig.Config
Expand Down Expand Up @@ -377,16 +378,9 @@ class PartlySaneSkies {
}

private fun registerEvent(obj: Any) {
try {
EVENT_BUS.register(obj)
} catch (e: Exception) {
e.printStackTrace()
}
try {
EventManager.register(obj)
} catch (e: Exception) {
e.printStackTrace()
}
runCatching { EVENT_BUS.register(obj) }.onFailure { it.printStackTrace() }
runCatching { EventManager.register(obj) }.onFailure { it.printStackTrace() }
runCatching { PSSEvents.register(obj) }.onFailure { it.printStackTrace() }
}

// Method runs every tick
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// Written by ThatGravyBoat.
// See LICENSE for copyright and license notices.
//

package me.partlysanestudios.partlysaneskies.api.events
ThatGravyBoat marked this conversation as resolved.
Show resolved Hide resolved

import org.apache.logging.log4j.Logger
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

class EventHandler<T : PSSEvent>(private val logger: Logger) {

private val listeners: MutableList<Listener> = mutableListOf()
private var lastCancellableIndex: Int = -1

fun register(method: Method, instance: Any, options: PSSEvent.Subscribe) {
listeners.add(Listener(options, createEventConsumer(instance, method)))
if (options.receiveCancelled) lastCancellableIndex = listeners.size - 1
}

@Suppress("UNCHECKED_CAST")
private fun createEventConsumer(instance: Any, method: Method): Consumer<Any> {
try {
val handle = MethodHandles.lookup().unreflect(method)
return LambdaMetafactory.metafactory(
MethodHandles.lookup(),
"accept",
MethodType.methodType(Consumer::class.java, instance::class.java),
MethodType.methodType(Nothing::class.javaPrimitiveType, Object::class.java),
handle,
MethodType.methodType(Nothing::class.javaPrimitiveType, method.parameterTypes[0])
).target.bindTo(instance).invokeExact() as Consumer<Any>
} catch (e: Throwable) {
throw IllegalArgumentException("Method ${method.name} is not a valid event consumer", e)
}
}

fun post(
event: T,
errorHandler: (Throwable) -> Boolean = {
logger.error("Error occurred while handling event", it)
true
}
) {
for ((index, listener) in listeners.withIndex()) {
if (event.isCancelled) {
if (index >= lastCancellableIndex) break
else if (!listener.options.receiveCancelled) continue
}
try {
listener.consumer.accept(event)
} catch (e: Throwable) {
if (!errorHandler(e)) throw e
}
}

}

private data class Listener(val options: PSSEvent.Subscribe, val consumer: Consumer<Any>)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Written by ThatGravyBoat.
// See LICENSE for copyright and license notices.
//

package me.partlysanestudios.partlysaneskies.api.events

abstract class PSSEvent protected constructor() {

var isCancelled = false
private set

fun post() {
PSSEvents.post(this)
}

interface Cancellable {

fun cancel() {
(this as PSSEvent).isCancelled = true
}
}

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Subscribe(
val receiveCancelled: Boolean = false,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// Written by ThatGravyBoat.
// See LICENSE for copyright and license notices.
//

package me.partlysanestudios.partlysaneskies.api.events

import org.apache.logging.log4j.LogManager
import java.lang.reflect.Method

object PSSEvents {

private val handlers: MutableMap<Class<*>, EventHandler<*>> = mutableMapOf()
private val logger = LogManager.getLogger("Partly Sane Skies Events")

@Suppress("UNCHECKED_CAST")
private fun <T : PSSEvent> getHandler(event: Class<T>): EventHandler<T> {
return handlers.getOrPut(event) { EventHandler<T>(logger) } as EventHandler<T>
}

@Suppress("UNCHECKED_CAST")
private fun register(method: Method, instance: Any): SubscribeResponse? {
val subscribe = method.getAnnotation(PSSEvent.Subscribe::class.java) ?: return null
if (method.parameterCount == 0) return SubscribeResponse.NO_PARAMETERS
if (method.parameterCount > 1) return SubscribeResponse.TOO_MANY_PARAMETERS
val eventClass = method.parameterTypes[0]
if (!PSSEvent::class.java.isAssignableFrom(eventClass)) return SubscribeResponse.PARAMETER_NOT_EVENT
getHandler(eventClass as Class<PSSEvent>).register(method, instance, subscribe)
return SubscribeResponse.SUCCESS
}

fun register(instance: Any) {
var hadEvent = false
instance.javaClass.declaredMethods.forEach { method ->
val response = register(method, instance) ?: return@forEach
hadEvent = true
when (response) {
SubscribeResponse.NO_PARAMETERS, SubscribeResponse.TOO_MANY_PARAMETERS -> {
logger.warn("Event subscription on ${method.name} has an incorrect number of parameters (${method.parameterCount})")
}
SubscribeResponse.PARAMETER_NOT_EVENT -> {
logger.warn("Event subscription on ${method.name} does not have a parameter that extends ${PSSEvent::class.java.name}")
}
SubscribeResponse.SUCCESS -> {}
}
}
if (!hadEvent) {
logger.warn("No events found in ${instance.javaClass.name}")
}
}

fun post(event: PSSEvent) {
getHandler(event.javaClass).post(event)
}

private enum class SubscribeResponse {
NO_PARAMETERS,
TOO_MANY_PARAMETERS,
PARAMETER_NOT_EVENT,
SUCCESS
}
}
Loading