SiO2 EventBus vs. Guava EventBus

They are a bit different in other ways also.

Guava hinges only off of the event class. SiO2 hinges on the event type object. So you can have several event types that use the same event context object. For example, login/logoff might both use a User object as their context. In guava’s event bus you would have to wrap those in an event and have registered twice to get them.

In addition, SiO2’s event bus also supports traditional listeners. This can be nice if you have your own dispatch mechanism beyond the listener and don’t want to have to know ahead of time all of the different listeners you will need (ie: 90 different methods in Guava). For example, if you are distributing certain events over the network… one traditional listener can do that.

Let’s compare in more specific detail some things I think it’s worth pointing out.

From Guava’s javadocs: (EventBus (Guava: Google Core Libraries for Java 22.0 API))

Receiving Events

To receive events, an object should:

  1. Expose a public method, known as the event subscriber, which accepts a single argument of the type of event desired;
  2. Mark it with a Subscribe annotation;
  3. Pass itself to an EventBus instance’s register(Object) method.

In SiO2, you can register regular listeners or you can register an object with the appropriately named methods based on your event type. These methods do not need to be public (which is good because you almost never want to expose such methods as a public API).

So, if you have:

EventType<Player> joined = EventType.create("PlayerJoined", Player.class);
EventType<Player> joining = EventType.create("PlayerJoining", Player.class);
EventType<Player> leaving = EventType.create("PlayerLeaving", Player.class);
EventType<Player> left = EventType.create("PlayerLeft", Player.class);

Then you can have one object handle all of those:

class MyPlayerHandler {
    private void onPlayerJoined( Player player ) {
    ....
    }
    public void playerJoining( Player player ) {
    ....
    }
    protected void playerLeaving( Player player ) {
    ....
    }
    private void onPlayerLeft( Player player ) {
    ....
    }
}

Note: in that example I gave a bunch of different ways that the method naming and access will work. I don’t recommend doing that for real, it’s an example.

For me, it’s quite common to define protected or private methods right on the class that wants to know about stuff. (Say, an app state) without exposing them publicly. Otherwise, the typical pattern would be to create an inner class that you register… and quite often that’s just going to forward to internal private methods anyway.

Or in the not-app-state example, maybe I have a chat box that wants to watch player events. I don’t have to create a separate object, I can just put protected or private methods right on my class.

To register for the above events in SiO2’s event bus, you’d do something like:
EventBus.addListener(instanceOfMyPlayerHandler, joined, joining, leaving, left);

In Guava, I’d have to have four separate annotated methods, four separate event wrapper classes, and four separate calls to register().

Other differences, from Guava’s javadocs:

Subscribers should not, in general, throw. If they do, the EventBus will catch and log the exception.

In SiO2, events that throw exceptions are wrapped in an ErrorEvent and redispatched. So you could have some listeners watching for these erroring events… log them, display them in a UI, whatever.

Other difference, from Guava’s javadocs:

Dead Events

If an event is posted, but no registered subscribers can accept it, it is considered “dead.” To give the system a second chance to handle dead events, they are wrapped in an instance of DeadEvent and reposted.

SiO2’s event bus will return true/false from the publishEvent() method if the message was delivered or not. It’s a stylistic difference but I felt it more likely that the thing publishing might care (or not) if their event was delivered rather than that being a globally handlable state. (Note: the static publish() seems to not return the boolean… I think that’s a bug.)

Which brings me to another difference, SiO2’s EventBus can be used as instances like Guava’s but there is also a static singleton that the whole application can easily use to coordinate/register for events. To me, it’s almost always the case (and certainly in games) that something like a decoupled event bus would want wide exposure rather than having an instance passed all over the application. For application servers and stuff, maybe not (but then you can also have separate instances)… but for games, certainly one static one is super convenient.

This is one of the very very few cases where I think I did better than Guava in the flexibility department for very little extra complexity. Though note that I didn’t repurpose my API for an AsyncEventBus like they did… but still I don’t think that matters.

For games, I think my pattern is superior. But I would, wouldn’t I? :slight_smile:

Edit: added a javadoc link for Guava since I quoted it. Bolded the SiO2 references so it is easier to distinguish when I’m talking about one library or the other…and highlighting SiO2 since that’s what we are talking about.

Edit 2: for completeness, here is SiO2’s code with relatively sparse javadoc for EventBus: https://github.com/Simsilica/SiO2/blob/master/src/main/java/com/simsilica/event/EventBus.java

ie: sparse because there is no nice large over-arching description (yet) like for Guava but the methods are well documented and there is a class level description.

3 Likes