diff options
author | bush-did-711 <39170869+bush-did-711@users.noreply.github.com> | 2022-03-30 23:03:09 -1000 |
---|---|---|
committer | bush-did-711 <39170869+bush-did-711@users.noreply.github.com> | 2022-03-30 23:03:09 -1000 |
commit | 0f5fdefed68d4d54f08d8e9fc9dc3a0e098f93a1 (patch) | |
tree | 5f773a9927046a0a0c41c4fb01bcda48dc3cbf6b /src/main/kotlin/me/bush | |
parent | 1246f0f644eb1fca30f92c601941937506ca2beb (diff) | |
download | eventbus-kotlin-0f5fdefed68d4d54f08d8e9fc9dc3a0e098f93a1.tar.gz eventbus-kotlin-0f5fdefed68d4d54f08d8e9fc9dc3a0e098f93a1.tar.bz2 eventbus-kotlin-0f5fdefed68d4d54f08d8e9fc9dc3a0e098f93a1.zip |
started adding tests, more refactoring (not done)
Diffstat (limited to 'src/main/kotlin/me/bush')
7 files changed, 130 insertions, 107 deletions
diff --git a/src/main/kotlin/me/bush/illnamethislater/CancelState.kt b/src/main/kotlin/me/bush/illnamethislater/CancelState.kt new file mode 100644 index 0000000..270d88f --- /dev/null +++ b/src/main/kotlin/me/bush/illnamethislater/CancelState.kt @@ -0,0 +1,46 @@ +package me.bush.illnamethislater + +import sun.misc.Unsafe +import kotlin.reflect.KClass +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.full.declaredMembers +import kotlin.reflect.full.isSubclassOf +import kotlin.reflect.jvm.javaField +import kotlin.reflect.typeOf + +/** + * @author bush + * @since 1.0.0 + */ +internal fun interface CancelledState { + + /** + * Will either return false, cast [event] to [Event] and return [Event.cancelled], or use + * [Unsafe.getBoolean] to get the value of the "cancelled" field of an external event. + * + * @author bush + * @since 1.0.0 + */ + fun isCancelled(event: Any): Boolean + + companion object { + private val UNSAFE = Unsafe::class.declaredMembers.single { it.name == "theUnsafe" }.handleCall() as Unsafe + + // This is really just for interoperability with forge events, but ig it would work with anything + private val CANCELLED_NAMES = arrayOf("canceled", "cancelled") + private val NOT_CANCELLABLE by lazy { CancelledState { false } } + private val OFFSETS = hashMapOf<KClass<*>, Long>() + + fun cancelStateOf(type: KClass<*>, bus: EventBus): CancelledState { + if (type.isSubclassOf(Event::class)) return CancelledState { (it as Event).cancelled } + if (!bus.externalSupport) return NOT_CANCELLABLE + type.allMembers.filter { it.name in CANCELLED_NAMES && it.returnType == typeOf<Boolean>() } + .filterIsInstance<KMutableProperty1<*, *>>().toList().let { + if (it.isEmpty()) return NOT_CANCELLABLE + if (it.size != 1) bus.logger.warn("Multiple possible cancel fields found for event type $type") + OFFSETS[type] = UNSAFE.objectFieldOffset(it[0].javaField) + return CancelledState { UNSAFE.getBoolean(it, OFFSETS[type]!!) } + } + } + } +} diff --git a/src/main/kotlin/me/bush/illnamethislater/Event.kt b/src/main/kotlin/me/bush/illnamethislater/Event.kt index 39360eb..dedd4df 100644 --- a/src/main/kotlin/me/bush/illnamethislater/Event.kt +++ b/src/main/kotlin/me/bush/illnamethislater/Event.kt @@ -1,19 +1,16 @@ package me.bush.illnamethislater /** - * A base class for events that can be cancelled. + * A base class for events that can be cancelled. More information can be found + * [here](https://github.com/therealbush/eventbus-kotlin) * - * If [cancellable] is true, your event can be [cancelled]. - * - * If [cancelled] is true, listeners with lower priority will not receive it unless: + * If [cancellable] is `true`, your event can be [cancelled], where future listeners will not receive it unless: * * They have [Listener.receiveCancelled] set to `true`. - * * A future listener with [Listener.receiveCancelled] sets [cancelled] to `false` + * * A previous listener with [Listener.receiveCancelled] sets [cancelled] back to `false` * * @author bush * @since 1.0.0 */ -// TODO: 3/27/2022 ducks or a way to cancel anything (can't put a custom annotation on forge events) -// store cancellable info at subscribe time, do not calculate it in post abstract class Event { var cancelled = false set(value) { diff --git a/src/main/kotlin/me/bush/illnamethislater/EventBus.kt b/src/main/kotlin/me/bush/illnamethislater/EventBus.kt index 66424f7..01d90cd 100644 --- a/src/main/kotlin/me/bush/illnamethislater/EventBus.kt +++ b/src/main/kotlin/me/bush/illnamethislater/EventBus.kt @@ -4,18 +4,23 @@ import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger import kotlin.reflect.KClass +// TODO: 3/30/2022 Refactor some stuff + /** * [A simple event dispatcher](http://github.com/therealbush/eventbus-kotlin) * * @author bush * @since 1.0.0 */ -class EventBus(private val logger: Logger = LogManager.getLogger()) { +class EventBus( // todo encapsulation?? + internal val logger: Logger = LogManager.getLogger("EVENTBUS"), + internal val externalSupport: Boolean = true +) { private val listeners = hashMapOf<KClass<*>, ListenerGroup>() private val subscribers = mutableSetOf<Any>() /** - * blah blah annotation properties override listener properties + * doc */ infix fun subscribe(subscriber: Any): Boolean { return if (subscriber in subscribers) false @@ -38,15 +43,15 @@ class EventBus(private val logger: Logger = LogManager.getLogger()) { */ fun register(listener: Listener, subscriber: Any = Any()): Any { listener.subscriber = subscriber - listeners.computeIfAbsent(listener.type, ::ListenerGroup).addListener(listener) + listeners.computeIfAbsent(listener.type) { ListenerGroup(CancelledState.cancelStateOf(listener.type, this)) } + .addListener(listener) subscribers += subscriber return subscriber } /** - * + * doc */ - // doc infix fun unsubscribe(subscriber: Any) = subscribers.remove(subscriber).apply { if (this) listeners.entries.removeIf { it.value.removeFrom(subscriber) @@ -55,10 +60,10 @@ class EventBus(private val logger: Logger = LogManager.getLogger()) { } /** - * Posts an event. + * Posts an event. doc */ - // doc infix fun post(event: Any) = listeners[event::class]?.let { group -> + // TODO: 3/30/2022 rewrite this all lol priority isn't even set up yet group.sequential.forEach { if (!group.cancelState.isCancelled(event) || it.receiveCancelled) { it.listener(event) diff --git a/src/main/kotlin/me/bush/illnamethislater/Listener.kt b/src/main/kotlin/me/bush/illnamethislater/Listener.kt index 9e7e25e..f7a2a81 100644 --- a/src/main/kotlin/me/bush/illnamethislater/Listener.kt +++ b/src/main/kotlin/me/bush/illnamethislater/Listener.kt @@ -3,32 +3,35 @@ package me.bush.illnamethislater import kotlin.reflect.KClass /** - * This class is not intended to be used externally, use [listener] instead. - * - * You *could* use this, and it would work fine, however - * [listener] lets us use reified types for a cleaner look. + * This class is not intended to be used externally, use [listener] instead. You *could* use this, + * and it would work fine however you would have to specify the type explicitly. (ew!) * * @author bush * @since 1.0.0 */ class Listener @PublishedApi internal constructor( listener: (Nothing) -> Unit, + internal val type: KClass<*>, internal var priority: Int = 0, internal var parallel: Boolean = false, - internal var receiveCancelled: Boolean = false, - internal val type: KClass<*> + internal var receiveCancelled: Boolean = false ) { @Suppress("UNCHECKED_CAST") + // Generics have no benefit here, + // it is easier just to force cast. internal val listener = listener as (Any) -> Unit internal lateinit var subscriber: Any } /** + * doc later * + * @author bush + * @since 1.0.0 */ inline fun <reified T : Any> listener( priority: Int = 0, parallel: Boolean = false, receiveCancelled: Boolean = false, noinline listener: (T) -> Unit -) = Listener(listener, priority, parallel, receiveCancelled, T::class) +) = Listener(listener, T::class, priority, parallel, receiveCancelled) diff --git a/src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt b/src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt index d8003a4..cef2a0c 100644 --- a/src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt +++ b/src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt @@ -1,16 +1,14 @@ package me.bush.illnamethislater import java.util.concurrent.CopyOnWriteArrayList -import kotlin.reflect.KClass /** * @author bush * @since 1.0.0 */ -internal class ListenerGroup(type: KClass<*>) { +internal class ListenerGroup(val cancelState: CancelledState) { val parallel = CopyOnWriteArrayList<Listener>() val sequential = CopyOnWriteArrayList<Listener>() - val cancelState = cancelStateOf(type) val isEmpty get() = parallel.isEmpty() && sequential.isEmpty() diff --git a/src/main/kotlin/me/bush/illnamethislater/ReflectUtil.kt b/src/main/kotlin/me/bush/illnamethislater/ReflectUtil.kt new file mode 100644 index 0000000..d03fe58 --- /dev/null +++ b/src/main/kotlin/me/bush/illnamethislater/ReflectUtil.kt @@ -0,0 +1,57 @@ +package me.bush.illnamethislater + +import java.lang.reflect.Modifier +import kotlin.reflect.KCallable +import kotlin.reflect.KClass +import kotlin.reflect.KProperty +import kotlin.reflect.full.allSuperclasses +import kotlin.reflect.full.declaredMembers +import kotlin.reflect.jvm.isAccessible +import kotlin.reflect.jvm.javaField +import kotlin.reflect.jvm.javaGetter +import kotlin.reflect.typeOf + +/** + * Using [KClass.members] only returns public members, and using [KClass.declaredMembers] + * doesn't return inherited members. This returns all members, private and inherited. + * + * @author bush + * @since 1.0.0 + */ +internal val <T : Any> KClass<T>.allMembers + get() = (declaredMembers + allSuperclasses.flatMap { it.declaredMembers }).asSequence() + +/** + * Checks if a [KCallable] is static on the jvm, and handles invocation accordingly. + * I am not aware of a better alternative that works with `object` classes. + * + * @author bush + * @since 1.0.0 + */ +internal fun <R> KCallable<R>.handleCall(receiver: Any? = null): R { + isAccessible = true + return if (static) call() else call(receiver) +} + +/** + * Checks if the calling [KCallable] is a static java field. Because Kotlin likes to be funny, properties + * belonging to `object` classes are static, but their getters are not. If there is a getter (the property + * is not private), we will be accessing that, otherwise we check if the field is static with Java reflection. + * This also lets us support static listeners in Java code. + * + * @author bush + * @since 1.0.0 + */ +internal val KCallable<*>.static + get() = if (this !is KProperty<*> || javaGetter != null) false + else javaField?.let { Modifier.isStatic(it.modifiers) } ?: false + +/** + * Finds all listeners in a class. (properties and methods) + * + * @author bush + * @since 1.0.0 + */ +@Suppress("UNCHECKED_CAST") // This cannot fail +internal inline val KClass<*>.listeners + get() = allMembers.filter { it.returnType == typeOf<Listener>() } as Sequence<KCallable<Listener>> diff --git a/src/main/kotlin/me/bush/illnamethislater/Util.kt b/src/main/kotlin/me/bush/illnamethislater/Util.kt deleted file mode 100644 index ab486ab..0000000 --- a/src/main/kotlin/me/bush/illnamethislater/Util.kt +++ /dev/null @@ -1,83 +0,0 @@ -package me.bush.illnamethislater - -import sun.misc.Unsafe -import java.lang.reflect.Modifier -import kotlin.reflect.* -import kotlin.reflect.full.allSuperclasses -import kotlin.reflect.full.declaredMembers -import kotlin.reflect.full.isSubclassOf -import kotlin.reflect.full.isSubtypeOf -import kotlin.reflect.jvm.javaField -import kotlin.reflect.jvm.javaGetter - -// author bush -// since 1.0.0 - -/** - * Using [KClass.members] only returns public members, and - * using [KClass.declaredMembers] doesn't return inherited - * members. This returns all members, private and inherited. - */ -internal val <T : Any> KClass<T>.allMembers - get() = (declaredMembers + allSuperclasses.flatMap { it.declaredMembers }).asSequence() - -/** - * Checks if a [KCallable] is static on the jvm, and handles invocation accordingly. - * - * I am not aware of a better alternative that works with `object` classes. - */ -internal fun <R> KCallable<R>.handleCall(receiver: Any) = if (static) call() else call(receiver) - -/** - * Checks if the calling [KCallable] is a static java field. - * - * Because kotlin likes to be funny, properties belonging to - * `object` classes are static, but their getters are not. - * - * If there is a getter (the property is not private), - * we will be accessing it through that, so we can stop checking. - * - * Otherwise, we check if the field is static with java reflection. - */ -internal val KCallable<*>.static - get() = if (this !is KProperty<*> || javaGetter != null) false - else javaField?.let { Modifier.isStatic(it.modifiers) } ?: false - -/** - * Finds all listeners in a class. (properties and methods) - */ -@Suppress("UNCHECKED_CAST") // This cannot fail -internal inline val KClass<*>.listeners - get() = allMembers.filter { it.returnType == typeOf<Listener>() } as Sequence<KCallable<Listener>> - -internal fun interface CancelledState { - fun isCancelled(event: Any): Boolean -} - -private val unsafe = Unsafe::class.java.getDeclaredField("theUnsafe").let { - it.isAccessible = true - it.get(null) as Unsafe -} - -private val offsetMap = hashMapOf<KClass<*>, Long>() - -private val possibleNames = arrayOf("canceled", "cancelled") - -private val NOT_CANCELLABLE = CancelledState { false } - -internal fun cancelStateOf(type: KClass<*>) = when { - type.isSubclassOf(Event::class) -> CancelledState { (it as Event).cancelled } - else -> findCancelField(type)?.let { - offsetMap[type] = unsafe.objectFieldOffset(it.javaField) - CancelledState { event -> unsafe.getBoolean(event, offsetMap[type]!!) } - } ?: NOT_CANCELLABLE -} - -private fun findCancelField(type: KClass<*>) = type.allMembers - .filter { it.name in possibleNames && it.returnType == typeOf<Boolean>() } - .filterIsInstance<KMutableProperty1<*, *>>().toList().let { - if (it.isEmpty()) null else { - if (it.size != 1) TODO() // warn - it[0] - } - } |