aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/me/bush
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/me/bush')
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/CancelledState.kt8
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/Event.kt2
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/EventBus.kt85
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/Listener.kt40
-rw-r--r--src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt12
5 files changed, 97 insertions, 50 deletions
diff --git a/src/main/kotlin/me/bush/illnamethislater/CancelledState.kt b/src/main/kotlin/me/bush/illnamethislater/CancelledState.kt
index 80022ba..d6ff39d 100644
--- a/src/main/kotlin/me/bush/illnamethislater/CancelledState.kt
+++ b/src/main/kotlin/me/bush/illnamethislater/CancelledState.kt
@@ -19,12 +19,14 @@ internal fun interface CancelledState {
/**
* Returns whether [event] is cancelled or not. [event] should only ever be of the type
- * that was passed to [CancelledState.of], or this will cause an error.
+ * that was passed to [CancelledState.of], **or this will crash.**
*/
fun isCancelled(event: Any): Boolean
companion object {
- private val UNSAFE = Unsafe::class.declaredMembers.single { it.name == "theUnsafe" }.handleCall() as Unsafe
+ private val UNSAFE = runCatching {
+ Unsafe::class.declaredMembers.single { it.name == "theUnsafe" }.handleCall() as Unsafe
+ }.getOrNull() // soy jvm
private val CANCELLED_NAMES = arrayOf("canceled", "cancelled")
private val NOT_CANCELLABLE = CancelledState { false }
private val OFFSETS = hashMapOf<KClass<*>, Long>()
@@ -40,7 +42,7 @@ internal fun interface CancelledState {
// Find a field named "cancelled" or "canceled" that is a boolean, and has a backing field.
type.allMembers.filter { it.name in CANCELLED_NAMES && it.returnType == typeOf<Boolean>() }
.filterIsInstance<KMutableProperty<*>>().filter { it.javaField != null }.toList().let {
- if (it.isEmpty()) return NOT_CANCELLABLE
+ if (it.isEmpty() || UNSAFE == null) return NOT_CANCELLABLE
if (it.size != 1) config.logger.warn("Multiple possible cancel fields found for event type $type")
it[0].javaField!!.let { field ->
if (Modifier.isStatic(field.modifiers)) OFFSETS[type] = UNSAFE.staticFieldOffset(field)
diff --git a/src/main/kotlin/me/bush/illnamethislater/Event.kt b/src/main/kotlin/me/bush/illnamethislater/Event.kt
index 476a34a..2881c0e 100644
--- a/src/main/kotlin/me/bush/illnamethislater/Event.kt
+++ b/src/main/kotlin/me/bush/illnamethislater/Event.kt
@@ -23,7 +23,7 @@ abstract class Event {
/**
* Determines if this event can be [cancelled]. This does not have to return a constant value.
*/
- abstract val cancellable: Boolean
+ protected abstract val cancellable: Boolean
/**
* Sets [cancelled] to true.
diff --git a/src/main/kotlin/me/bush/illnamethislater/EventBus.kt b/src/main/kotlin/me/bush/illnamethislater/EventBus.kt
index 6b9b0bf..5b278e3 100644
--- a/src/main/kotlin/me/bush/illnamethislater/EventBus.kt
+++ b/src/main/kotlin/me/bush/illnamethislater/EventBus.kt
@@ -3,31 +3,32 @@ package me.bush.illnamethislater
import kotlin.reflect.KClass
/**
- * A simple event dispatcher
- *
- * [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething)
+ * [A simple event dispatcher.](https://github.com/therealbush/eventbus-kotlin#tododothething)
*
* @author bush
* @since 1.0.0
*/
class EventBus(private val config: Config = Config()) {
private val listeners = hashMapOf<KClass<*>, ListenerGroup>()
- private val subscribers = mutableSetOf<Any>()
+ private val subscribers = hashMapOf<Any, List<Listener>>()
/**
- * doc
+ * Searches [subscriber] for members that return [Listener] and registers them.
+ *
+ * This will not find top level listeners, use [register] instead.
+ *
+ * Returns `false` if [subscriber] was already subscribed, `true` otherwise.
*
* [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething)
*/
fun subscribe(subscriber: Any): Boolean {
return if (subscriber in subscribers) false
else runCatching {
- subscriber::class.listeners.forEach { member ->
- register(member.handleCall(subscriber).also {
- it.subscriber = subscriber
- })
- }
- subscribers.add(subscriber)
+ // Register every listener into a group, but also
+ // keep a separate list just for this subscriber.
+ subscribers[subscriber] = subscriber::class.listeners.map { member ->
+ register(member.handleCall(subscriber).also { it.subscriber = subscriber })
+ }.toList()
true
}.getOrElse {
config.logger.error("Unable to register listeners for subscriber $subscriber", it)
@@ -36,40 +37,64 @@ class EventBus(private val config: Config = Config()) {
}
/**
- * doc
+ * Unregisters all listeners belonging to [subscriber].
+ *
+ * This will not remove top level listeners, use [unregister] instead.
+ *
+ * Returns `true` if [subscriber] was subscribed, `false` otherwise.
*
* [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething)
*/
fun unsubscribe(subscriber: Any): Boolean {
- return subscribers.remove(subscriber).also { contains ->
- if (contains) listeners.entries.removeIf {
- it.value.unsubscribe(subscriber)
- it.value.sequential.isEmpty() && it.value.parallel.isEmpty()
- }
+ val contained = subscriber in subscribers
+ // Unregister every listener for this subscriber,
+ // and return null so the map entry is removed.
+ subscribers.computeIfPresent(subscriber) { _, listeners ->
+ listeners.forEach { unregister(it) }
+ null
}
+ return contained
}
/**
- * 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.
+ * Registers a [Listener] to this [EventBus].
*
* [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething)
*/
- fun register(listener: Listener) = listener.also {
- listeners.computeIfAbsent(it.type) { type -> ListenerGroup(type, config) }.register(it)
+ fun register(listener: Listener): Listener {
+ listeners.computeIfAbsent(listener.type) {
+ ListenerGroup(it, config)
+ }.register(listener)
+ return listener
}
/**
- * doc
+ * Unregisters a [Listener] from this [EventBus].
*
* [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething)
*/
- fun unregister(listener: Listener) = listener.also {
- listeners[it.type]?.unregister(it)
+ fun unregister(listener: Listener): Boolean {
+ return listeners[listener.type]?.let {
+ val contained = it.unregister(listener)
+ if (it.parallel.isEmpty() && it.sequential.isEmpty()) {
+ listeners.remove(listener.type)
+ }
+ contained
+ } ?: false
}
/**
- * Posts an event. doc
+ * Posts an [event] to every listener that accepts its type.
+ *
+ * Events are **not** queued: only listeners subscribed currently will be called.
+ *
+ * If [event] is a subclass of [Event], or has a field-backed mutable boolean property
+ * named "cancelled" or "canceled" and [Config.thirdPartyCompatibility] is `true`,
+ * it can be cancelled by a listener, and only future listeners with [Listener.receiveCancelled]
+ * will receive it.
+ *
+ * Sequential listeners are called in the order of [Listener.priority], and parallel
+ * listeners are called before or after, depending on the value of [Config.parallelFirst].
*
* [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething)
*/
@@ -79,13 +104,15 @@ class EventBus(private val config: Config = Config()) {
* Logs the subscriber count, total listener count, and listener count for every event type with at
* least one subscriber to [Config.logger]. Per-event counts are sorted from greatest to least listeners.
*
+ * **This may cause a [ConcurrentModificationException] if [register] or [subscribe] is called in parallel.**
+ *
* [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething)
* ```
* Subscribers: 5
- * Listeners: 8 sequential, 4 parallel
- * BushIsSoCool: 4, 2
- * OtherEvent: 3, 1
- * String: 1, 1
+ * Listeners: 8 sequential, 21 parallel
+ * BushIsSoCool: 4, 9
+ * OtherEvent: 1, 10
+ * String: 3, 0
*/
fun debugInfo() {
config.logger.info("Subscribers: ${subscribers.size}")
diff --git a/src/main/kotlin/me/bush/illnamethislater/Listener.kt b/src/main/kotlin/me/bush/illnamethislater/Listener.kt
index 7105baf..b2bb2ce 100644
--- a/src/main/kotlin/me/bush/illnamethislater/Listener.kt
+++ b/src/main/kotlin/me/bush/illnamethislater/Listener.kt
@@ -1,5 +1,6 @@
package me.bush.illnamethislater
+import java.util.function.Consumer
import kotlin.reflect.KClass
/**
@@ -26,16 +27,15 @@ class Listener @PublishedApi internal constructor(
}
/**
- * Creates a listener that can be held in a variable, returned from
- * a function or getter, or directly registered to an Eventbus.
+ * Creates a listener that can be held in a variable or returned from a function
+ * or getter belonging to an object to be subscribed with [EventBus.subscribe],
+ * or directly registered to an [EventBus] with [EventBus.register].
*
* [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething)
*
- * @param T The type of event to listen for. Inheritance has no effect here.
- * If [T] is a base class, subclass events will not be received.
- * @param priority The priority of this listener. This can be any integer.
- * Listeners with a higher [priority] will be invoked first.
- * @param parallel If a listener should be invoked in parallel with other [parallel] listeners, or sequentially. todo finish parallel
+ * @param T The **exact** (no inheritance) type of event to listen for.
+ * @param priority The priority of this listener, high to low.
+ * @param parallel If a listener should be invoked in parallel with other parallel listeners, or sequentially.
* @param receiveCancelled If a listener should receive events that have been cancelled by previous listeners.
* @param listener The body of the listener that will be invoked.
*/
@@ -45,3 +45,29 @@ inline fun <reified T : Any> listener(
receiveCancelled: Boolean = false,
noinline listener: (T) -> Unit
) = Listener(listener, T::class, priority, parallel, receiveCancelled)
+
+/**
+ * **This function is intended for use in Java code.**
+ *
+ * Creates a listener that can be held in a variable or returned from a function
+ * or getter belonging to an object to be subscribed with [EventBus.subscribe],
+ * or directly registered to an [EventBus] with [EventBus.register].
+ *
+ * [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething)
+ *
+ * @param type The **exact** (no inheritance) type of event to listen for.
+ * @param priority The priority of this listener, high to low.
+ * @param parallel If a listener should be invoked in parallel with other parallel listeners, or sequentially.
+ * @param receiveCancelled If a listener should receive events that have been cancelled by previous listeners.
+ * @param listener The body of the listener that will be invoked.
+ */
+@JvmOverloads
+fun <T : Any> listener(
+ type: Class<T>,
+ priority: Int = 0,
+ parallel: Boolean = false,
+ receiveCancelled: Boolean = false,
+ // This might introduce some overhead, but its worth
+ // not manually having to return "Kotlin.UNIT" from every Java listener.
+ listener: Consumer<T>
+) = Listener({ event: T -> listener.accept(event) }, type.kotlin, priority, parallel, receiveCancelled)
diff --git a/src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt b/src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt
index 7b2a039..fdcc7bc 100644
--- a/src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt
+++ b/src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt
@@ -32,20 +32,12 @@ internal class ListenerGroup(
/**
* Removes [listener] from this [ListenerGroup].
*/
- fun unregister(listener: Listener) {
- if (listener.parallel) parallel.remove(listener)
+ fun unregister(listener: Listener): Boolean {
+ return if (listener.parallel) parallel.remove(listener)
else sequential.remove(listener)
}
/**
- * Removes every listener whose subscriber is [subscriber].
- */
- fun unsubscribe(subscriber: Any) {
- parallel.removeIf { it.subscriber == subscriber }
- sequential.removeIf { it.subscriber == subscriber }
- }
-
- /**
* Posts an event to every listener. Returns true of the event was cancelled.
*/
fun post(event: Any): Boolean {