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
}
}
~}