Event Bus¶
An event bus is a system that is responsible of handling event dispatching and subscribing.
Each subsystem has its own event bus, so does the engine. The engine’s event bus is the main event bus, and all other event buses are dedicated to handling events for their respective subsystems.
The holder of an event bus is responsible for proceeding to the dispatch of events through the process method. For
example, a Subsystem will have its own EventBus processed by the core subsystem loop which runs on a separate
thread.
Design¶
Declaration¶
The engine’s EventBus class is declared as follows:
#pragma once
#include "bboc/engine/event/Event.hpp"
#include "bboc/engine/event/EventHandler.hpp"
#include <queue>
#include <vector>
#include <typeindex>
#include <unordered_map>
#include <mutex>
#include <memory>
namespace BBOC {
class EventBus final {
struct HandlerEntry {
EventHandlerFunction<Event> handler;
void *origin;
};
struct QueueEntry {
std::type_index type;
std::unique_ptr<Event> event;
};
using EventHandlers = std::vector<HandlerEntry>;
using Map = std::unordered_map<std::type_index, EventHandlers>;
public:
void subscribe(const std::type_index &type, EventHandlerFunction<Event> handler, void *origin = nullptr);
void unsubscribe(const std::type_index &type, EventHandlerFunction<Event> handler, void *origin = nullptr);
void unsubscribe(void *origin);
void emit(const std::type_info &type, std::unique_ptr<Event> &&event);
template<typename T, EventLike<T> = 0>
void subscribe(EventHandlerFunction<T> handler) {
std::type_index type(typeid(T));
this->subscribe(type, reinterpret_cast<EventHandlerFunction<Event>>(handler));
}
template<typename T, typename U, EventLike<T> = 0>
void subscribe(U *self, EventHandlerMethod<T, U> handler) {
std::type_index type(typeid(T));
this->subscribe(type, [self, handler](const Event &event) {
(self->*handler)(static_cast<const T &>(event));
}, self);
}
template<typename T, EventLike<T> = 0,
typename U>
void unsubscribe(T *self) {
this->unsubscribe(typeid(T), self);
}
template<typename T>
void unsubscribe(T *self) {
this->unsubscribe(static_cast<void *>(self));
}
template<typename T, EventLike<T> = 0>
void emit(T &&event) {
this->emit(typeid(T), std::make_unique<T>(event));
}
template<typename T, EventLike<T> = 0, typename... Args>
void emit(Args &&...args) {
T event(std::forward<Args>(args)...);
this->emit(typeid(T), std::make_unique<T>(std::move(event)));
}
void process();
private:
Map _handlers;
std::mutex _handlerMutex;
std::queue<QueueEntry> _eventQueue;
std::mutex _queueMutex;
};
}
Methods¶
The main methods which you are likely to use are subscribe, unsubscribe and emit. These methods are declared in
a few different forms so that member functions of classes and lambda functions can be used as event handlers.
Thread Safety¶
An event bus is designed to be thread-safe exclusively on event dispatching, and not on event subscribing.
This means that only concurrent calls to emit are safe, and not to subscribe or unsubscribe.
Events can be processed in a selected thread, so that e.g. event handlers which require exclusive access to a
non-multithreaded resource such as an SFML window can be executed in the same thread where the resource was created
from.
This means that emit calls can be performed on any thread, and you do not have to worry about concurrency issues
when emitting events unless if the resource being accessed is not on the same thread as the event handler.
In addition, the event received by an event handler is a const reference, so that the event handler cannot modify the
event’s data.
Usage in Classes¶
During the implementation of your classes, you’d likely want to expose a getEventBus method which returns a reference
to the event bus of the class, so that the end users can easily subscribe to the events that you are going to emit
during the lifetime of the class’ instance.
Subscribing and Unsubscribing¶
Subscribing (or registering) an event handler to an event bus is done by calling the subscribe method:
MyClass::MyClass() :
_eventHandlerLambda([](const EventType& event) {
// Handle the event here
}) {
// Privilege this form over the lambda form
eventBus.subscribe<EventType>(this, &MyClass::onEventOfType);
// Only use this form for simple event handlers or specific scenarios
eventBus.subscribe<EventType>(this->_eventHandlerLambda);
}
void MyClass::onEventOfType(const EventType& event) {
// Handle the event here
}
The same goes for unsubscribing (or unregistering) an event handler from an event bus, which is done by calling the
unsubscribe method:
MyClass::~MyClass() {
eventBus.unsubscribe<EventType>(this, &MyClass::onEventOfType);
eventBus.unsubscribe<EventType>(this->_eventHandlerLambda);
}
Warning
As explained in the Thread Safety section, you cannot call subscribe and unsubscribe
concurrently.
We recommend you to perform these operations in the constructor and destructor of your class, respectively. Doing dynamic subscribing and unsubscribing is not safe and can lead to undefined behavior, if you do not ensure a non concurrent access to the event bus object.
Event Inheritance¶
Event structures can be inherited from one another, so that you can have a base event structure and derived event structures.
Do note that the event handler will distinguish between the base event and the derived event, so that if you subscribe to the base event, you will not receive the derived event, and vice versa. In other words, the event bus will not attempt to resolve the inheritance hierarchy of the events.