Event Bus

Etcetera provides a basic event bus with a reflection-based hooking system a la @SubscribeEvent. This system was originally created for Facade.

First, some terminology. Events are fired on an event bus. Other code can then hook into that event type on that bus. Events can then be fired on that event bus and the bus will run all the hooks registered for that event type, passing the event object to each one in succession.

Event Bus

Creating an event bus just requires creating an EventBus object.

~import com.teamwizardry.librarianlib.etcetera.eventbus.EventBus;
~
~public class ThingWithEvents {
    public final EventBus BUS = new EventBus();
~}

Events

Events come in two flavors, cancelable and non-cancelable. I’ll be covering the non-cancelable events first since cancelable events just build on top of them.

Events typically have fields that hold details about what happened (e.g. what mouse button was clicked). These are typically final fields, since events shouldn’t be changing them. Some events want something from the hooks (e.g. allowing event hooks to constrain a value before it’s used), so they will have non-final fields that the hooks can modify. The data from the event object will then be read after firing it.

Simple events

Simple events are, well, simple. The only thing you need to do to create your own event is subclass Event.

~import com.teamwizardry.librarianlib.etcetera.eventbus.Event;
~
public class ExampleEvent extends Event {
    public final int action;

    public ExampleEvent(int action) {
        this.action = action;
    }
}

Cancelable events

Cancelable events are virtually identical to plain events, except that they have a cancel() method that will stop any further processing of the event (unless they request otherwise. I’ll get into that) and the code firing the event may interpret the event being canceled in some special way.

~import com.teamwizardry.librarianlib.etcetera.eventbus.CancelableEvent;
~
public class ExampleCancelableEvent extends CancelableEvent {
    public final int action;

    public ExampleCancelableEvent(int action) {
        this.action = action;
    }
}

Hooks

Hooks into events can be made one of two ways. Either by manually calling the hook method on the bus or by annotating a method with @Hook and passing an instance to the register method on the bus. Note that there’s no way to use @Hook on static methods.

These two blocks are functionally equivalent:

~public class ExampleHooks {
    public void manualHook() {
        BUS.hook(ExampleEvent.class, (ExampleEvent e) -> {
            // run code here
        });
    }
~}
~import com.teamwizardry.librarianlib.etcetera.eventbus.Hook;
~
~public class ExampleHooks {
    public void autoHook() {
        BUS.register(this);
    }

    // hook methods don't need to be public
    @Hook
    private void example(ExampleEvent e) {
        // run code here
    }
~}

Advanced events/hooks

Hook options

Similarly to Forge event hooks, Etcetera event hooks can have a priority and can request to still receive canceled events. The priority and receiveCanceled flag can be passed to the hook method or in the @Hook annotation, as the case may be. The priority is useful for canceling events, since you can request for your event to run before others. The priorities are, in the order they’re run, FIRST, EARLY, DEFAULT, LATE, LAST.

Per-hook state

Individual event hooks can hold some state, and your event can act on that by overriding the storePerHookState and loadPerHookState methods. Whatever value was returned from storePerHookState is passed to loadPerHookState the next time that hook is called, meaning each individual hook can have independent state.

~import com.teamwizardry.librarianlib.etcetera.eventbus.Event;
~import org.jetbrains.annotations.Nullable;
~
public class ExampleEventState extends Event {
    public final int delta;
    public int accumulator;

    public ExampleEventState(int delta) {
        this.delta = delta;
    }

    @Override
    protected void loadPerHookState(@Nullable Object state) {
        if(state != null) {
            accumulator = (Integer)state;
        } else {
            accumulator = 0;
        }
        accumulator += delta;
    }

    @Nullable
    @Override
    protected Object storePerHookState() {
        return accumulator;
    }
}
~import com.teamwizardry.librarianlib.etcetera.eventbus.Hook;
~
~public class ExampleHooks {
~    public void autoHook() {
~        BUS.register(this);
~    }
~
    // this will run every 100 "units" of delta. This is useful for mouse
    // scroll events, where lots of little deltas should add up to one step
    @Hook
    private void example(ExampleEventState e) {
        while(e.accumulator >= 100) {
            e.accumulator -= 100;
            // do the thing
        }
    }
~}