aboutsummaryrefslogtreecommitdiff
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
parentdcd9eaad6d679307c54d18ea16bbd54abae0fc35 (diff)
downloadeventbus-kotlin-e14ab8f09fd5cf2a8917d43c7edc9a402283b0a3.tar.gz
eventbus-kotlin-e14ab8f09fd5cf2a8917d43c7edc9a402283b0a3.tar.bz2
eventbus-kotlin-e14ab8f09fd5cf2a8917d43c7edc9a402283b0a3.zip
not done
-rw-r--r--README.md2
-rw-r--r--build.gradle.kts4
-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
-rw-r--r--src/test/kotlin/Main.kt58
8 files changed, 183 insertions, 110 deletions
diff --git a/README.md b/README.md
index 320bdb7..3b80c54 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,4 @@
#EVENTBUS-KOTLIN
this isnt done yet i just wanted to get the gh up
+
+still not done dfsdofdfomsdkflmsdf
diff --git a/build.gradle.kts b/build.gradle.kts
index b882228..a1450bd 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -19,7 +19,9 @@ dependencies {
implementation("org.apache.logging.log4j:log4j-api:2.15.0")
implementation("org.apache.logging.log4j:log4j-core:2.15.0")
+ // Additional Kotlin libraries required for some features
implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.10")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
}
tasks.test {
@@ -28,4 +30,4 @@ tasks.test {
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
-} \ No newline at end of file
+}
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>>
diff --git a/src/test/kotlin/Main.kt b/src/test/kotlin/Main.kt
index 0933c97..dc6e194 100644
--- a/src/test/kotlin/Main.kt
+++ b/src/test/kotlin/Main.kt
@@ -1,26 +1,58 @@
import me.bush.illnamethislater.*
+import org.apache.logging.log4j.Level
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.core.config.Configurator
/**
* @author bush
- * @since 3/13/2022
+ * @since 1.0.0
*/
fun main() {
- EventBus().run {
+ Configurator.setRootLevel(Level.INFO)
- subscribe(Subscriber())
+// EventBus().run {
+//
+// subscribe(Subscriber())
+//
+// post("String")
+//
+// val key = register(listener<Int> {
+// println(it)
+// })
+//
+// val topLevelListenerKey = register(topLevelListener())
+//
+// unsubscribe(key)
+//
+// unsubscribe(topLevelListenerKey)
+//
+// debugInfo()
+// }
- post("Object()")
+ val not = NotDuck()
+ not.wtf()
+ doDuck(not)
+ //doDuck(Any())
+}
+
+fun topLevelListener() = listener<Int> { println("topLevelListener(): $it") }
- register(listener<String> {
- println("it")
- })
+class Subscriber {
+
+ val listener0 get() = listener<String>(500, true, false) {
+ println(it.uppercase())
}
}
-class Subscriber {
-// clean up and writr tests
- @EventListener
- fun onDeez() = listener(listener = { e: String ->
- println(e.uppercase())
- }, receiveCancelled = false, )
+fun doDuck(any: Any) {
+}
+
+class NotDuck {
+ fun wtf() {
+ println("wtf")
+ }
+}
+
+interface Duck {
+ fun wtf()
}