From b196c7c2fe06d84e862648a8bf25058f72c6c0b1 Mon Sep 17 00:00:00 2001 From: therealbush Date: Sat, 2 Apr 2022 00:17:29 -1000 Subject: Nearly done --- .../me/bush/illnamethislater/CancelledState.kt | 8 +- src/main/kotlin/me/bush/illnamethislater/Event.kt | 2 +- .../kotlin/me/bush/illnamethislater/EventBus.kt | 85 +++++--- .../kotlin/me/bush/illnamethislater/Listener.kt | 40 +++- .../me/bush/illnamethislater/ListenerGroup.kt | 12 +- src/test/java/TestJava.java | 80 ++++++++ src/test/kotlin/Test.kt | 189 ------------------ src/test/kotlin/TestKotlin.kt | 215 +++++++++++++++++++++ 8 files changed, 392 insertions(+), 239 deletions(-) create mode 100644 src/test/java/TestJava.java delete mode 100644 src/test/kotlin/Test.kt create mode 100644 src/test/kotlin/TestKotlin.kt 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, 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() } .filterIsInstance>().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, ListenerGroup>() - private val subscribers = mutableSetOf() + private val subscribers = hashMapOf>() /** - * 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 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 listener( + type: Class, + 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 +) = 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,19 +32,11 @@ 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. */ diff --git a/src/test/java/TestJava.java b/src/test/java/TestJava.java new file mode 100644 index 0000000..c6c77a8 --- /dev/null +++ b/src/test/java/TestJava.java @@ -0,0 +1,80 @@ +import me.bush.illnamethislater.Listener; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.Configurator; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import me.bush.illnamethislater.EventBus; +import org.apache.logging.log4j.LogManager; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import static me.bush.illnamethislater.ListenerKt.listener; + +/** + * I was getting noclassdeffound when trying to load this Java + * class in the other test and I don't care enough to fix it. + * + * @author bush + * @since 1.0.0 + */ +@TestInstance(Lifecycle.PER_CLASS) +public class TestJava { + private static boolean thisShouldChange; + private boolean thisShouldChangeToo; + private EventBus eventBus; + private final Logger logger = LogManager.getLogger(); + + @BeforeAll + public void setup() { + Configurator.setRootLevel(Level.ALL); + + // Test that init works + logger.info("Initializing"); + eventBus = new EventBus(); + + // Ensure no npe + logger.info("Logging empty debug info"); + eventBus.debugInfo(); + + // Test that basic subscribing reflection works + logger.info("Subscribing"); + eventBus.subscribe(this); + + logger.info("Testing Events"); + } + + @AfterAll + public void unsubscribe() { + logger.info("Unsubscribing"); + eventBus.unsubscribe(this); + eventBus.debugInfo(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Test + public void javaSubscriberTest() { + eventBus.subscribe(this); + eventBus.post(new MyEvent()); + Assertions.assertTrue(TestJava.thisShouldChange); + Assertions.assertTrue(this.thisShouldChangeToo); + // TODO: 4/2/2022 fix calling from java + } + + public Listener listener = listener(MyEvent.class, 200, event -> { + Assertions.assertEquals(event.getSomeString(), "donda"); + event.setSomeString("donda 2"); + this.thisShouldChangeToo = true; + }); + + public static Listener someStaticListener() { + return listener(MyEvent.class, 100, event -> { + Assertions.assertEquals(event.getSomeString(), "donda 2"); + thisShouldChange = true; + }); + } +} diff --git a/src/test/kotlin/Test.kt b/src/test/kotlin/Test.kt deleted file mode 100644 index 2bdb445..0000000 --- a/src/test/kotlin/Test.kt +++ /dev/null @@ -1,189 +0,0 @@ -import me.bush.illnamethislater.Event -import me.bush.illnamethislater.EventBus -import me.bush.illnamethislater.listener -import org.apache.logging.log4j.Level -import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.core.config.Configurator -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.TestInstance.Lifecycle -import kotlin.random.Random - -/** - * I don't know how to do these.... - * - * @author bush - * @since 1.0.0 - */ -@TestInstance(Lifecycle.PER_CLASS) -class Test { - private lateinit var eventBus: EventBus - private val logger = LogManager.getLogger() - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - @BeforeAll - fun setup() { - Configurator.setRootLevel(Level.ALL) - - // Test that init works - logger.info("Initializing") - eventBus = EventBus() - - // Ensure no npe - logger.info("Logging empty debug info") - eventBus.debugInfo() - - // Test that basic subscribing reflection works - logger.info("Subscribing") - eventBus.subscribe(this) - - logger.info("Testing Events") - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests debug info format - @Test - fun debugInfoTest() { - eventBus.debugInfo() - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests autoboxing and internal data structure against primitives. - @Test - fun primitiveListenerTest() { - val random = Random.nextInt() - eventBus.post(random) - Assertions.assertEquals(random, primitiveTestValue) - } - - private var primitiveTestValue = 0 - - val primitiveListener = listener { - primitiveTestValue = it - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests unsubscribing of listeners which don't belong to a subscriber. - @Test - fun freeListenerTest() { - // Register listener and keep the value - val listener = eventBus.register(listener { - freeListenerTestValue = it - }) - val valueOne = "i love bush's eventbus <3" - val valueTwo = "sdklasdjsakdsadlksadlksdl" - // Will change the value - eventBus.post(valueOne) - Assertions.assertEquals(valueOne, freeListenerTestValue) - // Remove the listener - eventBus.unregister(listener) - // No effect - eventBus.post(valueTwo) - // Value will not change - Assertions.assertEquals(valueOne, freeListenerTestValue) - } - - private var freeListenerTestValue: String? = null - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests priority and receiveCancelled functionality. - @Test - fun myEventListenerTest() { - val event = MyEvent() - eventBus.post(event) - Assertions.assertEquals(event.lastListener, "myEventListener3") - } - - // First to be called; highest priority. - val myEventListener0 = listener(priority = 10) { - Assertions.assertEquals(it.lastListener, "") - it.lastListener = "myEventListener0" - it.cancel() - } - - // Will not be called; second-highest priority, no receiveCancelled. - val myEventListener1 - get() = listener(priority = 0) { - Assertions.assertTrue(false) - } - - // Second to be called; has receiveCancelled and can un-cancel the event. - fun myEventListener2() = listener(priority = Int.MIN_VALUE + 100, receiveCancelled = true) { - Assertions.assertEquals(it.lastListener, "myEventListener0") - it.lastListener = "myEventListener2" - it.cancelled = false - } - - // Last to be called; does not have receiveCancelled, but the last listener un-cancelled the event. - fun myEventListener3() = listener(priority = Int.MIN_VALUE) { - Assertions.assertEquals(it.lastListener, "myEventListener2") - it.lastListener = "myEventListener3" - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests external event cancel state functionality. - @Test - fun externalEventListenerTest() { - - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests parallel invocation functionality. todo how will this work with cancellability - @Test - fun parallelListenerTest() { - - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests reflection against singleton object classes. - @Test - fun objectSubscriberTest() { - - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - - // todo what else? - // todo ensure boolean functions return proper value (subscribe, unsubscribe, etc) -} - -// todo test changing cancellability -class MyEvent : Event() { - override val cancellable = true - - var lastListener = "" -} - -class ExternalEvent0 { - var canceled = false -} - -class ExternalEvent1 { - var cancelled = false -} - -// Should give us a warning about duplicates -class ExternalEvent2 { - var canceled = false - var cancelled = false -} diff --git a/src/test/kotlin/TestKotlin.kt b/src/test/kotlin/TestKotlin.kt new file mode 100644 index 0000000..2ae6313 --- /dev/null +++ b/src/test/kotlin/TestKotlin.kt @@ -0,0 +1,215 @@ +import me.bush.illnamethislater.Event +import me.bush.illnamethislater.EventBus +import me.bush.illnamethislater.listener +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.core.config.Configurator +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle +import kotlin.random.Random + +/** + * I don't know how to do these.... + * + * @author bush + * @since 1.0.0 + */ +@TestInstance(Lifecycle.PER_CLASS) +class Test { + private lateinit var eventBus: EventBus + private val logger = LogManager.getLogger() + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @BeforeAll + fun setup() { + Configurator.setRootLevel(Level.ALL) + + // Test that init works + logger.info("Initializing") + eventBus = EventBus() + + // Ensure no npe + logger.info("Logging empty debug info") + eventBus.debugInfo() + + // Test that basic subscribing reflection works + logger.info("Subscribing") + eventBus.subscribe(this) + + logger.info("Testing Events") + } + + @AfterAll + fun unsubscribe() { + logger.info("Unsubscribing") + eventBus.unsubscribe(this) + eventBus.debugInfo() + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Tests debug info format + @Test + fun debugInfoTest() { + eventBus.debugInfo() + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Tests autoboxing and internal data structure against primitives. + @Test + fun primitiveListenerTest() { + val random = Random.nextInt() + eventBus.post(random) + Assertions.assertEquals(random, primitiveTestValue) + } + + private var primitiveTestValue = 0 + + val primitiveListener = listener { + primitiveTestValue = it + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Tests unsubscribing of listeners which don't belong to a subscriber. + @Test + fun freeListenerTest() { + // Register listener and keep the value + val listener = eventBus.register(listener { + freeListenerTestValue = it + }) + val valueOne = "i love bush's eventbus <3" + val valueTwo = "sdklasdjsakdsadlksadlksdl" + // Will change the value + eventBus.post(valueOne) + Assertions.assertEquals(valueOne, freeListenerTestValue) + // Remove the listener + eventBus.unregister(listener) + // No effect + eventBus.post(valueTwo) + // Value will not change + Assertions.assertEquals(valueOne, freeListenerTestValue) + } + + private var freeListenerTestValue: String? = null + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Tests priority and receiveCancelled functionality. + @Test + fun myEventListenerTest() { + val event = MyEvent() + eventBus.post(event) + Assertions.assertEquals(event.lastListener, "myEventListener3") + } + + // First to be called; highest priority. + val myEventListener0 = listener(priority = 10) { + Assertions.assertEquals(it.lastListener, "") + it.lastListener = "myEventListener0" + it.cancel() + } + + // Will not be called; second-highest priority, no receiveCancelled. + val myEventListener1 + get() = listener(priority = 0) { + Assertions.assertTrue(false) + } + + // Second to be called; has receiveCancelled and can un-cancel the event. + fun myEventListener2() = listener(priority = Int.MIN_VALUE + 100, receiveCancelled = true) { + Assertions.assertEquals(it.lastListener, "myEventListener0") + it.lastListener = "myEventListener2" + it.cancelled = false + } + + // Last to be called; does not have receiveCancelled, but the last listener un-cancelled the event. + fun myEventListener3() = listener(priority = Int.MIN_VALUE) { + Assertions.assertEquals(it.lastListener, "myEventListener2") + it.lastListener = "myEventListener3" + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Tests external event cancel state functionality. + @Test + fun externalEventListenerTest() { + var unchanged = "this will not change" + // Cancels the event + eventBus.register(listener(200) { + it.canceled = true + }) + // This shouldn't be called + eventBus.register(listener(100) { + unchanged = "changed" + }) + eventBus.post(ExternalEvent()) + Assertions.assertEquals(unchanged, "this will not change") + // Tests that duplicates are detected, and that both + // "canceled" and "cancelled" are detected as valid fields + eventBus.register(listener {}) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Tests parallel invocation functionality. + @Test + fun parallelListenerTest() { + + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Tests reflection against singleton object classes. + @Test + fun objectSubscriberTest() { + eventBus.subscribe(ObjectSubscriber) + eventBus.post(Unit) + Assertions.assertTrue(ObjectSubscriber.willChange) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // todo test thread safety + // todo ensure boolean functions return proper value (subscribe, unsubscribe, etc) +} + +object ObjectSubscriber { + var willChange = false + + fun listener() = listener { + willChange = true + } +} + +class MyEvent : Event() { + override val cancellable = true + + var lastListener = "" + var someString = "donda" +} + +class ExternalEvent { + var canceled = false +} + +// Should give us a warning about duplicates +class ExternalDuplicates { + var canceled = false + var cancelled = false +} -- cgit