A lightweight, modular C++ event system designed for flexibility, clarity, and priority-based event handling.
Handlers and events are sorted independently, allowing precise control over execution order and data flow.
-
Murmur3 hashing
-
Priority‑sorted dispatch
-
Deterministic handler ordering
-
Component‑based payloads
-
Zero‑dependency
-
Engine‑ready
Adding a new event type is simple:
Event PlayerDiedEvent(Event::EventType("PlayerDied", Event::kPriorityNormal));
// or with custom parameters
Event PlayerDiedEvent(Event::EventType("EventNameString", somePriorityLevel));- Event Name String: Avoid spaces — use
HelloWorldinstead ofHello World. - Priority Levels: There are predefined constants (e.g.,
kPriorityNormal,kPriorityMax),
but you can use custom integers as well.
Handlers are defined as:
using EventHandler = std::pair<int, std::function<void(const Event&)>>;To register a handler, you just need:
- The event name or hash.
- A priority level for sorting handlers.
- A functor, lambda, or function pointer for the callback.
eventManager.AddHandler("NewEvent", {
somePriorityLevel,
[](const Event& event) { /* handle event */ }
});Handlers must be removed manually when they are no longer needed.
Registered handlers are not consumed during event processing.
eventManager.RemoveHandler("NewEvent", Handler);
// or using hashed event name
eventManager.RemoveHandler(Event::EventType::HashEventString("PlayerDied"), Handler);You can pre-hash event names or store both the hash and handler for faster registration/removal.
Payloads act like lightweight components.
Each payload:
- Requires an owner pointer to the event container.
- Is implicitly linked when added via
AddComponent(). - Should be read-only — preventing unintended state changes before other handlers process the event.
For example, in a collision payload, store entity IDs instead of pointers so other systems can safely resolve them.
- Handlers are sorted per event using a
std::set.
→ Higher priority values run first. - Event priority is separate from handler priority.
→ Changing one doesn’t affect the other.
Here’s a simple example showing event registration, processing, and cleanup:
void OnPlayerMoved(const Event& event)
{
std::cout << "PlayerMoved handled — Data payload: "
<< event.GetComponent<PayloadTest>()->ToString() << "\n";
}
void OnPlayerDied(const Event& event)
{
std::cout << "PlayerDied handled\n";
}
void OnPlayerDiedPriority(const Event& event)
{
std::cout << "PlayerDied handled with HIGH priority\n";
}
int main()
{
EventManager eventManager;
std::string player = "Player variable to capture";
// Register handlers
eventManager.AddHandler(Event::EventType::HashEventString("PlayerMoved"), {
0, [player]([[maybe_unused]] const Event& event) { std::cout << player << "\n"; }
});
eventManager.AddHandler(Event::EventType::HashEventString("PlayerMoved"),
{ Event::kPriorityNormal, OnPlayerMoved });
eventManager.AddHandler("PlayerDied", { Event::kPriorityNormal, OnPlayerDied });
eventManager.AddHandler(Event::EventType::HashEventString("PlayerDied"),
{ Event::kPriorityMax, OnPlayerDiedPriority });
// Create events
const Event PlayerDiedEvent(Event::EventType("PlayerDied", Event::kPriorityNormal));
Event playerMovedEvent(Event::EventType("PlayerMoved", Event::kPriorityMax));
if (playerMovedEvent.AddComponent<PayloadTest>())
std::cout << "Payload component added successfully\n";
// Push events to queue
eventManager.PushEvent(PlayerDiedEvent);
eventManager.PushEvent(playerMovedEvent);
// Process events
eventManager.ProcessEvents();
// Cleanup
eventManager.RemoveHandler(Event::EventType::HashEventString("PlayerMoved"), {
0, [player]([[maybe_unused]] const Event& event) { std::cout << player << "\n"; }
});
eventManager.RemoveHandler(Event::EventType::HashEventString("PlayerMoved"),
{ Event::kPriorityNormal, OnPlayerMoved });
eventManager.RemoveHandler(Event::EventType::HashEventString("PlayerDied"),
{ Event::kPriorityNormal, OnPlayerDied });
eventManager.RemoveHandler("PlayerDied",
{ Event::kPriorityMax, OnPlayerDiedPriority });
return 0;
}- Event handlers and event priorities are completely independent.
- Events are queued and processed; handlers are persistent until removed.
- Payloads behave like components, ensuring clean, safe data flow.
- Handlers are sorted automatically — higher priority executes first.