Olivki / eventbus

A publish-subscribe style event system for Kotlin/JVM and Java.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

eventbus

Maven Central

This is a simple publish-subscribe style event handling library for Kotlin/JVM, with support for using it from Java.

It has support for annotation based event-listener registration @Subscribed fun listener(event: Event) { ... }, and callback based registration eventBus.on<Event> { ... }.

The callback based variant is based on suspend functions.

The library comes with 3 EventExecutorFactory implementations provided by default, a reflection based one, a MethodHandle based one, and an ASM based one that dynamically compiles new Java dispatcher classes at runtime. The ASM based factory is by far the fastest, with the MethodHandle one being second, and the reflection one being the slowest. Do note that the gap between the ASM approach and the MethodHandle approach is quite large, but the gap between the MethodHandle approach and the reflection one isn’t that large.

While the ASM factory is the fastest, it is the only factory that can’t create dispatchers for private functions, reflection can invoke them if invokePrivate was set to true when created, and method handles can invoke them if the MethodHandle.Lookup instance passed to the EventBus.subscribe function belongs to the actual listener instance the private function is defined in. None of the factories can create dispatchers that handle package-private functions.

Installation

repositories {
    mavenCentral()
}

dependencies {
    implementation("net.ormr.eventbus:eventbus-core:${EVENTBUS_VERSION}")
}

Usage

Kotlin

New Instance

To create a new EventBus instance for custom event and/or listener types, invoke EventBus<CustomListener, CustomEvent>(), for a new instance using the default <Any, Event> types, invoke EventBus.newDefault().

Both functions optionally take a parameter for the EventExecutorFactory to use.

Managing Listeners

Registering a new annotation based event listener:

class ListenerClass(eventBus: EventBus<Any, Event>) {
    init {
        eventBus.subscribe(this)
    }

    @Subscribed
    fun listener(event: MessageEvent) {
        println(event.message)
    }
}

class MessageEvent(val message: String) : Event

fun main() {
    val eventBus = EventBus.newDefault()
    val listener = ListenerClass(eventBus)
    eventBus.fire(MessageEvent("Hello, World!"))
    // 'Hello, World!' will be printed to std-out
}

The annotation based event listener can also be unregistered:

fun main() {
    // ...
    eventBus.unsubscribe(listener)
    eventBus.fire(MessageEvent("Goodbye, World!"))
    // 'Goodbye, World!' will not be printed to std-out
}

Registering a new callback based event listener:

suspend fun main() {
    val eventBus = EventBus.newDefault()
    eventBus.on<BasicEvent> { println(message) }
    // for actual suspended firing
    eventBus.fireSuspended(MessageEvent("Hello, World!"))
    // for blocking firing, equivalent of doing runBlocking { eventBus.fireSuspended(event) }
    // eventBus.fire(MessageEvent("Hello, World!")
    // 'Hello, World!' will be printed to std-out
}

Callback based event listeners can not be removed/unsubscribed, as they hold no real identity to query against.


Java

New Instance

To create a new EventBus instance for custom event and/or listener types, invoke new EventBus<>(CustomListener.class, CustomEvent.class), for a new instance using the default <Object, Event> types, invoke EventBus.newDefault().

Both functions optionally take a parameter for the EventExecutorFactory to use.

Managing Listeners

Registering a new annotation based event listener:

public class Example {
    public class ListenerClass {
        public ListenerClass(EventBus<Object, Event> eventBus) {
            eventBus.subscribe(this, MethodHandle.lookup());
        }

        @Subscribed
        public void listener(final MessageEvent event) {
            System.out.println(event.message());
        }
    }

    public record MessageEvent(String message) implements Event {}

    public static void main(String[] args) {
        var eventBus = EventBus.newDefault();
        var listener = new ListenerClass(eventBus);
        eventBus.fire(new MessageEvent("Hello, World!"));
        // 'Hello, World!' will be printed to std-out
    }
}

The annotation based event listener can also be unregistered:

public class Example {
    public static void main(String[] args) {
        // ...
        eventBus.unsubscribe(listener);
        eventBus.fire(MessageEvent("Goodbye, World!"));
        // 'Goodbye, World!' will not be printed to std-out
    }
}

Registering a new callback based event listener:

public class Example {
    public static void main(String[] args) {
        var eventBus = EventBus.newDefault();
        eventBus.on(MessageEvent.class, event -> System.out.println(event.message()));
        // Java does not know anything of Kotlin suspensions, and therefore when firing an event from the Java world
        // it will always be fired in a blocking manner.
        eventBus.fire(new MessageEvent("Hello, World!"));
        // 'Hello, World!' will be printed to std-out
    }
}

Callback based event listeners can not be removed/unsubscribed, as they hold no real identity to query against.

About

A publish-subscribe style event system for Kotlin/JVM and Java.


Languages

Language:Kotlin 100.0%