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-27 23:14:27 -1000
committerbush-did-711 <39170869+bush-did-711@users.noreply.github.com>2022-03-27 23:14:27 -1000
commite14ab8f09fd5cf2a8917d43c7edc9a402283b0a3 (patch)
treea919b08b9cd716e03ffbf534737d79dac6129863 /src/main/kotlin/me/bush
parentdcd9eaad6d679307c54d18ea16bbd54abae0fc35 (diff)
downloadeventbus-kotlin-e14ab8f09fd5cf2a8917d43c7edc9a402283b0a3.tar.gz
eventbus-kotlin-e14ab8f09fd5cf2a8917d43c7edc9a402283b0a3.tar.bz2
eventbus-kotlin-e14ab8f09fd5cf2a8917d43c7edc9a402283b0a3.zip
not done
Diffstat (limited to 'src/main/kotlin/me/bush')
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/Event.kt16
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/EventBus.kt95
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/Listener.kt49
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/ListenerList.kt43
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/Util.kt26
5 files changed, 133 insertions, 96 deletions
diff --git a/src/main/kotlin/me/bush/illnamethislater/Event.kt b/src/main/kotlin/me/bush/illnamethislater/Event.kt
index 2430067..39360eb 100644
--- a/src/main/kotlin/me/bush/illnamethislater/Event.kt
+++ b/src/main/kotlin/me/bush/illnamethislater/Event.kt
@@ -3,9 +3,17 @@ package me.bush.illnamethislater
/**
* A base class for events that can be cancelled.
*
+ * If [cancellable] is true, your event can be [cancelled].
+ *
+ * If [cancelled] is true, listeners with lower priority will not receive it unless:
+ * * They have [Listener.receiveCancelled] set to `true`.
+ * * A future listener with [Listener.receiveCancelled] sets [cancelled] to `false`
+ *
* @author bush
- * @since 3/13/2022
+ * @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) {
@@ -14,5 +22,7 @@ abstract class Event {
abstract val cancellable: Boolean
- fun cancel() { cancelled = false }
-} \ No newline at end of file
+ fun cancel() {
+ cancelled = false
+ }
+}
diff --git a/src/main/kotlin/me/bush/illnamethislater/EventBus.kt b/src/main/kotlin/me/bush/illnamethislater/EventBus.kt
index f35b65d..4de6bc5 100644
--- a/src/main/kotlin/me/bush/illnamethislater/EventBus.kt
+++ b/src/main/kotlin/me/bush/illnamethislater/EventBus.kt
@@ -2,81 +2,84 @@ package me.bush.illnamethislater
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
-import java.util.UUID
-import java.util.concurrent.atomic.AtomicLong
import kotlin.reflect.KClass
-import kotlin.reflect.full.findAnnotation
/**
+ * [A simple event dispatcher](http://github.com/therealbush/eventbus-kotlin)
+ *
* @author bush
- * @since 3/13/2022
+ * @since 1.0.0
*/
-class EventBus(private val logger: Logger = LogManager.getLogger(), private val strict: Boolean = true) {
- private val listeners = hashMapOf<KClass<*>, MutableList<Listener>>()
- private val subscribers = hashSetOf<Any>()
-
- // TODO: 3/26/2022 coroutine shit idk
+class EventBus(private val logger: Logger = LogManager.getLogger()) {
+ private val listeners = hashMapOf<KClass<*>, ListenerList>()
+ private val subscribers = mutableSetOf<Any>()
/**
* blah blah annotation properties override listener properties
*/
- fun subscribe(subscriber: Any) {
- if (subscribers.add(subscriber)) {
- subscriber::class.allMembers.filterReturnType<Listener>().forEach { property ->
- register(property.handleCall(subscriber).apply {
- // If the annotation is present, update listener settings from it
- // If it is not, and we are in strict mode, continue to the next property
- property.findAnnotation<EventListener>()?.let {
- priority = it.priority
- receiveCancelled = it.receiveCancelled
- } ?: if (strict) return@forEach
- }, subscriber)
+ infix fun subscribe(subscriber: Any): Boolean {
+ return if (subscriber in subscribers) false
+ else runCatching {
+ subscriber::class.listeners.forEach {
+ register(it.handleCall(subscriber), subscriber)
}
+ true
+ }.getOrElse {
+ logger.error("Unable to register listeners for subscriber $subscriber", it)
+ false
}
}
/**
+ * Registers a listener (which may not belong to any subscriber) to this [EventBus]. If no object
+ * is given, a key will be returned which can be used in [unsubscribe] to remove the listener.
*
+ * The
*/
- fun register(listener: Listener, subscriber: Any = ListenerKey()): Any {
- putListener(listener.also { it.subscriber = subscriber })
+ fun register(listener: Listener, subscriber: Any = Any()): Any {
+ listeners.getOrPut(listener.type) { ListenerList() }
+ .add(listener.also { it.subscriber = subscriber })
+ subscribers += subscriber
return subscriber
}
/**
- * Puts a listener into its respective list and sorts the list.
+ *
*/
- private fun putListener(listener: Listener) {
- listeners.getOrPut(listener.type) { mutableListOf() }.run {
- add(listener)
- sortBy(Listener::priority)
+ // doc
+ infix fun unsubscribe(subscriber: Any) = subscribers.remove(subscriber).apply {
+ if (this) listeners.entries.removeIf {
+ it.value.removeFrom(subscriber)
+ it.value.isEmpty
}
}
/**
- *
+ * Posts an event.
*/
- fun unsubscribe(subscriber: Any) {
- if (subscribers.remove(subscriber)) {
- listeners.values.forEach { it.removeIf(subscriber::equals) }
- }
- }
+ // doc
+ infix fun post(event: Any) = listeners[event::class]?.post(event)
/**
- * ur mom
+ * Logs the subscriber count, total listener count, and listener count
+ * for every event type with at least one subscriber to [logger].
+ * Per-event counts are sorted from greatest to least listeners.
+ * ```
+ * Subscribers: 5
+ * Listeners: 8 sequential, 4 parallel
+ * SomeInnerClass: 4, 2
+ * OtherEvent: 3, 1
+ * String: 1, 1
*/
- fun post(event: Any) = listeners[event::class].let { list ->
- if (list == null || list.isEmpty()) false
- else if (event is Event) {
- list.forEach {
- if (!event.cancelled || it.receiveCancelled) {
- it.listener(event)
- }
+ fun debugInfo() {
+ logger.info(StringBuilder().apply {
+ append("\nSubscribers: ${subscribers.size}")
+ val sequential = listeners.values.sumOf { it.sequential.size }
+ val parallel = listeners.values.sumOf { it.parallel.size }
+ append("\nListeners: $sequential sequential, $parallel parallel")
+ listeners.entries.sortedByDescending { it.value.sequential.size + it.value.parallel.size }.forEach {
+ append("\n${it.key.simpleName}: ${it.value.sequential.size}, ${it.value.parallel.size}")
}
- event.cancelled
- } else {
- list.forEach { it.listener(event) }
- false
- }
+ }.toString())
}
}
diff --git a/src/main/kotlin/me/bush/illnamethislater/Listener.kt b/src/main/kotlin/me/bush/illnamethislater/Listener.kt
index f4626ca..9e7e25e 100644
--- a/src/main/kotlin/me/bush/illnamethislater/Listener.kt
+++ b/src/main/kotlin/me/bush/illnamethislater/Listener.kt
@@ -2,50 +2,33 @@ package me.bush.illnamethislater
import kotlin.reflect.KClass
-// document
-annotation class EventListener(val priority: Int = 0, val receiveCancelled: Boolean = false)
-
/**
- * This class is not intended to be used externally, it is just
- * a wrapper for a listener function and some related properties.
+ * This class is not intended to be used externally, use [listener] instead.
*
- * Use [listener] instead.
+ * You *could* use this, and it would work fine, however
+ * [listener] lets us use reified types for a cleaner look.
*
* @author bush
- * @since 3/13/2022
+ * @since 1.0.0
*/
class Listener @PublishedApi internal constructor(
- internal val type: KClass<*>,
- internal val listener: (Any) -> Unit,
+ listener: (Nothing) -> Unit,
internal var priority: Int = 0,
- internal var receiveCancelled: Boolean = false
+ internal var parallel: Boolean = false,
+ internal var receiveCancelled: Boolean = false,
+ internal val type: KClass<*>
) {
- internal var subscriber: Any? = null
+ @Suppress("UNCHECKED_CAST")
+ internal val listener = listener as (Any) -> Unit
+ internal lateinit var subscriber: Any
}
-@Suppress("UNCHECKED_CAST")
-inline fun <reified T : Any> listener(
- noinline listener: (T) -> Unit
-) = Listener(T::class, listener as (Any) -> Unit)
-
-@Suppress("UNCHECKED_CAST")
-inline fun <reified T : Any> listener(
- priority: Int,
- noinline listener: (T) -> Unit
-) = Listener(T::class, listener as (Any) -> Unit, priority)
-
-@Suppress("UNCHECKED_CAST")
-inline fun <reified T : Any> listener(
- receiveCancelled: Boolean,
- noinline listener: (T) -> Unit
-) = Listener(T::class, listener as (Any) -> Unit, receiveCancelled = receiveCancelled)
-
-// The above aren't needed as the next is functionally identical, but they
-// let us avoid (imo ugly) named parameters or a lambda inside parentheses.
-
-@Suppress("UNCHECKED_CAST")
+/**
+ *
+ */
inline fun <reified T : Any> listener(
priority: Int = 0,
+ parallel: Boolean = false,
receiveCancelled: Boolean = false,
noinline listener: (T) -> Unit
-) = Listener(T::class, listener as (Any) -> Unit, priority, receiveCancelled)
+) = Listener(listener, priority, parallel, receiveCancelled, T::class)
diff --git a/src/main/kotlin/me/bush/illnamethislater/ListenerList.kt b/src/main/kotlin/me/bush/illnamethislater/ListenerList.kt
new file mode 100644
index 0000000..9bebb87
--- /dev/null
+++ b/src/main/kotlin/me/bush/illnamethislater/ListenerList.kt
@@ -0,0 +1,43 @@
+package me.bush.illnamethislater
+
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import java.util.concurrent.CopyOnWriteArrayList
+
+/**
+ * @author bush
+ * @since 1.0.0
+ */
+internal class ListenerList {
+ val sequential = CopyOnWriteArrayList<Listener>()
+ val parallel = CopyOnWriteArrayList<Listener>()
+
+ val isEmpty get() = sequential.isEmpty() && parallel.isEmpty()
+
+ fun add(listener: Listener) {
+ if (listener.parallel) parallel += listener
+ else sequential += listener
+ }
+
+ fun removeFrom(subscriber: Any) {
+ sequential.removeIf(subscriber::equals)
+ parallel.removeIf(subscriber::equals)
+ }
+
+ fun post(event: Any): Boolean {
+ // todo thsi
+ parallel.run {
+ if (isNotEmpty()) runBlocking {
+ forEach { // credit kami blue for the idea
+ launch { it.listener(event) }
+ }
+ }
+ }
+ sequential.run {
+ if (isNotEmpty()) forEach {
+ it.listener(event)
+ }
+ }
+ return false
+ }
+}
diff --git a/src/main/kotlin/me/bush/illnamethislater/Util.kt b/src/main/kotlin/me/bush/illnamethislater/Util.kt
index 6c0eb9a..5285718 100644
--- a/src/main/kotlin/me/bush/illnamethislater/Util.kt
+++ b/src/main/kotlin/me/bush/illnamethislater/Util.kt
@@ -12,10 +12,10 @@ import kotlin.reflect.full.starProjectedType
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaGetter
-// @author bush
-// @since 3/13/2022
+// 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.
@@ -23,14 +23,14 @@ import kotlin.reflect.jvm.javaGetter
internal val <T : Any> KClass<T>.allMembers
get() = declaredMembers + allSuperclasses.flatMap { it.declaredMembers }
-/**
+/*
* 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
@@ -45,13 +45,11 @@ internal val KCallable<*>.static
get() = if (this !is KProperty<*> || javaGetter != null) false
else javaField?.let { Modifier.isStatic(it.modifiers) } ?: false
-@Suppress("UNCHECKED_CAST") // This cannot fail
-internal inline fun <reified T : Any> List<KCallable<*>>.filterReturnType() =
- filter { it.returnType == T::class.starProjectedType } as List<KCallable<T>>
-
-/**
- * A simple class returned as a "key" for listeners that are not
- * members of a class, just to make its intentions clearer. This
- * can be used in [EventBus.unsubscribe] to remove the listener.
+/*
+ * Finds all listeners in a class. (properties and methods)
*/
-internal class ListenerKey \ No newline at end of file
+@Suppress("UNCHECKED_CAST") // This cannot fail
+internal inline val KClass<*>.listeners
+ get() = allMembers.asSequence().filter {
+ it.returnType == Listener::class.starProjectedType
+ } as Sequence<KCallable<Listener>>