Skip to content

EntropyEngine::Core::EventBus

Type-safe publish-subscribe event system for decoupled communication. More…

#include <EventBus.h>

Name
using size_tHandlerId
using std::function< void(const std::any &)>EventHandler
Name
~EventBus() =default
template <typename EventType >
bool
unsubscribe(HandlerId handlerId)
Cancel your subscription - stop receiving these events.
template <typename EventType >
HandlerId
subscribe(std::function< void(const EventType &)> handler)
Sign up to receive a specific type of event - like subscribing to a newsletter.
template <typename EventType >
void
publish(const EventType & event)
Broadcast an event to all interested parties - fire and forget!
EventBus &operator=(const EventBus & ) =delete
EventBus &operator=(EventBus && ) =delete
boolhasSubscribers() const
Quick check if anyone is listening to anything at all.
size_tgetTotalSubscriptions() const
Count total subscriptions across all event types.
template <typename EventType >
size_t
getSubscriberCount() const
Count how many subscribers are listening for a specific event type.
size_tgetMemoryUsage() const
Estimate how much memory this EventBus is using.
voidclear()
Nuclear option: remove all subscriptions for all event types.
EventBus() =default
EventBus(const EventBus & ) =delete
EventBus(EventBus && ) =delete
class EntropyEngine::Core::EventBus;

Type-safe publish-subscribe event system for decoupled communication.

EventBus implements a publish-subscribe pattern where components can publish events and subscribe to specific event types without direct knowledge of each other. Publishers and subscribers only need to share common event type definitions.

Unlike traditional global event buses, this implementation is lightweight enough to support thousands of instances - one per WorkGraph, game entity, or UI widget. Memory usage remains manageable even with many instances.

Key features:

  • Type-safe: Can’t accidentally subscribe to the wrong event type
  • Thread-safe: Supports concurrent publishing from any thread
  • Zero virtual functions: No vtable overhead
  • Exception-safe: One bad handler won’t crash the whole system
  • Self-cleaning: Removes empty handler lists when unsubscribing

Common use cases:

  • Decoupling UI from game logic
  • Progress notifications from long-running operations
  • State change notifications in complex systems
  • Any time you’re tempted to add Yet Another Callback Parameter

Complexity characteristics:

  • Subscribe: O(1) amortized
  • Publish: O(n) where n = subscribers for that event type
// Define your event types - just plain structs
struct PlayerHealthChanged {
int oldHealth;
int newHealth;
bool isDead() const { return newHealth <= 0; }
};
// Subscribe from anywhere
EventBus& bus = gameEntity.getEventBus();
auto healthId = bus.subscribe<PlayerHealthChanged>([this](const auto& e) {
updateHealthBar(e.newHealth);
if (e.isDead()) {
showGameOverScreen();
}
});
// Publish from anywhere else
bus.publish(PlayerHealthChanged{100, 0}); // RIP player
// Clean up when done
bus.unsubscribe<PlayerHealthChanged>(healthId);
using EntropyEngine::Core::EventBus::HandlerId = size_t;
using EntropyEngine::Core::EventBus::EventHandler = std::function<void(const std::any&)>;
~EventBus() =default
template <typename EventType >
inline bool unsubscribe(
HandlerId handlerId
)

Cancel your subscription - stop receiving these events.

Parameters:

Template Parameters:

  • EventType The same event type you subscribed to

Return: true if successfully unsubscribed, false if ID wasn’t found

Pass the ID from subscribe() to remove handler. Cleans up empty lists. Thread-safe.

// Always save your subscription IDs!
class GameUI {
EventBus& bus;
EventBus::HandlerId healthSubId;
void onEnable() {
healthSubId = bus.subscribe<HealthChanged>([this](auto& e) {
updateHealthBar(e.newHealth);
});
}
void onDisable() {
bus.unsubscribe<HealthChanged>(healthSubId);
}
};
template <typename EventType >
inline HandlerId subscribe(
std::function< void(const EventType &)> handler
)

Sign up to receive a specific type of event - like subscribing to a newsletter.

Parameters:

  • handler Your callback - lambda, function, or callable

Template Parameters:

  • EventType The event struct/class you want to receive

Return: A unique ID for this subscription (save it!)

Handler called for each published event. Returns ID for unsubscribing. Thread-safe.

// Simple lambda subscription
auto id = bus.subscribe<MouseClick>([](const MouseClick& e) {
std::cout << "Click at (" << e.x << ", " << e.y << ")\n";
});
// Capture local state
int clickCount = 0;
bus.subscribe<MouseClick>([&clickCount](const MouseClick& e) {
clickCount++;
if (clickCount >= 10) {
unlockAchievement("ClickHappy");
}
});
// Member function binding
bus.subscribe<GameStateChanged>(
std::bind(&UIManager::onGameStateChanged, this, std::placeholders::_1)
);
template <typename EventType >
inline void publish(
const EventType & event
)

Broadcast an event to all interested parties - fire and forget!

Parameters:

  • event The event data to send

Template Parameters:

  • EventType The event type you’re publishing

Sends to all subscribers of this type. Handlers called synchronously. Safe: copies handlers, catches exceptions. Thread-safe.

// Fire a simple event
bus.publish(LevelCompleted{currentLevel, score, timeElapsed});
// Events can have methods
struct DamageEvent {
Entity* target;
int amount;
DamageType type;
bool isLethal() const {
return target->health <= amount;
}
};
bus.publish(DamageEvent{player, 50, DamageType::Fire});
// Publishing to no subscribers is fine - nothing happens
bus.publish(ObscureDebugEvent{}); // No subscribers? No problem!
EventBus & operator=(
const EventBus &
) =delete
EventBus & operator=(
EventBus &&
) =delete
inline bool hasSubscribers() const

Quick check if anyone is listening to anything at all.

Return: true if any handlers are registered, false if completely empty

inline size_t getTotalSubscriptions() const

Count total subscriptions across all event types.

Return: Sum of all subscriptions for all event types

template <typename EventType >
inline size_t getSubscriberCount() const

Count how many subscribers are listening for a specific event type.

Template Parameters:

  • EventType The event type to check

Return: Number of active subscribers for this event type

Skip expensive work if nobody’s listening. Good for debugging too.

// Optimize expensive operations
if (bus.getSubscriberCount<DetailedPhysicsUpdate>() > 0) {
// Only calculate detailed physics if someone cares
auto details = calculateExpensivePhysicsDetails();
bus.publish(DetailedPhysicsUpdate{details});
}
inline size_t getMemoryUsage() const

Estimate how much memory this EventBus is using.

Return: Approximate bytes used by this EventBus

Includes object + dynamic allocations. Close enough for profiling.

// Memory profiling
if (bus.getMemoryUsage() > 1024 * 1024) { // 1MB
LOG_WARN("EventBus using {}KB of memory!",
bus.getMemoryUsage() / 1024);
}
inline void clear()

Nuclear option: remove all subscriptions for all event types.

Wipes clean. All handler IDs become invalid. For shutdown cleanup. Thread-safe.

EventBus() =default
EventBus(
const EventBus &
) =delete
EventBus(
EventBus &&
) =delete

Updated on 2026-01-26 at 17:14:35 -0500