Skip to content

A threadsafe C++ implementation of the EventBus idiom

Notifications You must be signed in to change notification settings

mmcshane/eventbus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EventBus

It's an event bus. Subscribe to events. Publish events. Quoting the documentation for Google's implementation of the EventBus concept in Guava:

EventBus allows publish-subscribe-style communication between components without requiring the components to explicitly register with one another (and thus be aware of each other). It is designed exclusively to replace traditional […] in-process event distribution using explicit registration. It is not a general-purpose publish-subscribe system, nor is it intended for interprocess communication.

Caution: there are downsides to every pattern or idiom. EventBuses are no different. Read about some of the down-sides and alternatives and really think about if this is an approach you want to adopt.

Features

This library is fairly small and being header-only should be easy to integrate into existing projects. A C++11 compiler is required. All operations are threadsafe -- establishing and/or ending subscriptions concurrent with event publication is safe for any number of threads. Moreover, event publication is wait-free within the event bus. It may be the case that event handlers are not wait-free.

Event subscriptions are aware of event-type polymorphism. That is to say that given a pair of event types called Base and Derived where Base is a base class of Derived, a subscription to Base event types will be invoked when an event of type Derived is published. This is accomplished with a slight augmentation to normal C++ inheritance -- Derived must extend Base via the mpm::enable_polymorphic_dispatch class rather than directly. Rather than the following "normal" inheritance:

struct Derived : Base
{
}

Derived should be defined as

struct Derived : mpm::enable_polymorphic_dispatch<Derived, Base>
{
}

Defining Derived in this manner will allow and event of this type to be delivered as both Derived and Base.

It is not required that mpm::enable_polymorphic_dispatch be used. Any instance of a C++ object type can be published, however if polymorphic delivery is desired then mpm::enable_polymorphic_dispatch must be used.

Example Usage

Let's define some events

struct my_base_event : mpm::enable_polymorphic_dispatch<my_base_event>
{
    int x = 12;
};

struct my_derived_event
    : mpm::enable_polymorphic_dispatch<my_derived_event, my_base_event>
{
    int y = 5;
};

struct my_object {};

struct my_non_polymorphic_event : my_object
{
    int foo = -1;
};

Here's a quick look at publishing and subscribing to a polymorphic event

mpm::eventbus ebus;

// two subscriptions - 1 for my_base_event and 1 for my_derived_event

auto base_subscription = mpm::scoped_subscription<my_base_event> {
    ebus, [](const my_base_event& mbe) noexcept {
        std::cout << "handling a base event" << mbe.x;
    }
);

auto derived_subscription = mpm::scoped_subscription<my_derived_event> {
    ebus, [](const my_derived_event& mde) noexcept {
        std::cout << "handling a derived event" << mde.y;
    }
);

// publish
ebus.publish(my_derived_event{});

// subscriptions terminate at scope exit

Some things worth noting here

  • Both event handlers will fire
  • If you have a C++14 compiler, the callback can be declared with auto (e.g. const auto& event), removing the duplication in specifying the event type.

For non-polymorphic dispatch, any object type can be published and it will be handled by handlers for only that exact type.

mpm::eventbus ebus;

// two subscriptions - 1 for my_object, 1 for my_non_polymorphic_event

auto base_subscription = mpm::scoped_subscription<my_object> {
    ebus, [](const my_object& mo) noexcept {
        std::cout << "handling a my_object";
    }
};

auto non_poly_subscription = mpm::scoped_subscription<my_non_polymorhpic_event> {
    ebus, [](const my_non_polymorphic_event& mnpe) noexcept {
        std::cout << "handling a my_non_polymorphic_event " << mnpe.foo;
    }
);

// publish
ebus.publish(my_non_polymorphic_event{});

// subscriptions terminate at scope exit

Note with the above example that only the handler for my_non_polymorphic_event will fire because the inheritance relationship was not established via mpm::enable_polymorphic_dispatch.

Building

There's really no build needed as this is a header-only library, however if you want to run the unit tests or generate docs you can use the cmake build. To perform an out-of-tree build

$ cd "$(mktemp -d)"
$ cmake /path/to/eventbus/repository
$ make && make test
$ make docs # generates doxygen documentation under $PWD/docs

TODO

  • As it stands, publishing events from within event handlers is allowed. It's not clear that this is good.
  • Implement an asynchronous publication proxy
  • Maybe allow non-noexcept subscribers but unsubscribe them if they throw?
  • Pull leftright as an external project rather than embedding it under thirdparty/