aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/me/bush
diff options
context:
space:
mode:
authorbush-did-711 <39170869+bush-did-711@users.noreply.github.com>2022-03-30 23:03:09 -1000
committerbush-did-711 <39170869+bush-did-711@users.noreply.github.com>2022-03-30 23:03:09 -1000
commit0f5fdefed68d4d54f08d8e9fc9dc3a0e098f93a1 (patch)
tree5f773a9927046a0a0c41c4fb01bcda48dc3cbf6b /src/main/kotlin/me/bush
parent1246f0f644eb1fca30f92c601941937506ca2beb (diff)
downloadeventbus-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')
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/CancelState.kt46
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/Event.kt11
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/EventBus.kt19
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/Listener.kt17
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt4
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/ReflectUtil.kt57
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/Util.kt83
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]
- }
- }