Typed Messaging
The Malena message system provides typed, enum-keyed communication between any two framework objects — components, plugins, or application code — without direct references between them.
Two Communication Systems
Malena has two complementary communication systems. Knowing which to use is the key.
| Event System | Message System | |
|---|---|---|
| Key type | String ("click", "hover") | User-defined enum |
| Payload | Optional sf::Event | Any typed value (int, struct, etc.) |
| Who fires it | UIManager (input) or dispatchers | Your application code |
| Best for | Input, hover, focus, per-frame update | App-level signals between objects |
| Trait | Messenger / Subscribable | Messenger |
Use the message system when you want one object to tell another object something happened — without the two objects knowing about each other directly.
Send and Receive
Any class that inherits ml::Messenger can both send and receive typed messages. Components gain Messenger by passing it as a trait to ComponentWith.
// GameEvents.h — shared between sender and receiver
enum class GameEvent { Start, Stop, ScoreChanged, LevelUp };
// Send an int payload
sendMessage(GameEvent::ScoreChanged, 42);
// Send with no meaningful payload — use bool as a unit type
sendMessage(GameEvent::Start, true);
// Send a struct
struct LevelData { int level; float timeLimit; };
sendMessage(GameEvent::LevelUp, {3, 90.f});
// Receive the score and update the HUD
onMessage(GameEvent::ScoreChanged, [this](const int& score) {
_scoreText.setString("Score: " + std::to_string(score));
});
// Receive with no meaningful payload
onMessage(GameEvent::Start, [this](const bool&) {
startGameTimer();
showStartAnimation();
});
// Receive a struct
onMessage(GameEvent::LevelUp, [this](const LevelData& d) {
_levelText.setString("Level " + std::to_string(d.level));
_timer.setLimit(d.timeLimit);
});
<int>(GameEvent::ScoreChanged) will never accidentally receive a <float>(GameEvent::ScoreChanged) message — they are different subscriptions.Using Manifest Event Enums
If your component uses a manifest, you can declare your message enum inside the manifest under enum class Event. This keeps message definitions co-located with the component that owns them.
class HUDManifest : public ml::Manifest
{
public:
enum class Images { Background };
enum class Flag { Visible };
// Message events declared in the manifest
enum class Event { ScoreChanged, HealthChanged, GameOver };
};
class HUD : public ml::ComponentWith
{
public:
HUD()
{
// Event aliases are in scope — no HUDManifest:: prefix needed
onMessage(Event::ScoreChanged, [this](const int& s) {
_scoreText.setString(std::to_string(s));
});
onMessage(Event::HealthChanged, [this](const int& hp) {
updateHealthBar(hp);
});
onMessage(Event::GameOver, [this](const bool&) {
showGameOverScreen();
});
}
};
ml::Messenger as a trait to ComponentWith gives the component sendMessage and onMessage methods. Without it you still have access to the raw MessageManager API, but the trait wraps it cleanly.Unsubscribing
Subscriptions registered via onMessage are automatically removed when the object is destroyed — you do not need to manually unsubscribe in a destructor. For runtime removal use offMessage or offAllMessages.
// Remove one specific subscription
offMessage(GameEvent::ScoreChanged);
// Remove all message subscriptions on this object
offAllMessages();
offMessage or sendMessage from inside a message callback. The MessageManager defers removal until after the current delivery completes, preventing iterator-invalidation crashes.Messages vs Events — When to Use Each
Choose based on who fires it and what it carries.
| Scenario | Use |
|---|---|
| Player clicks a button | Event — onClick |
| Score increases by 10 | Message — sendMessage<int>(ScoreChanged, 10) |
| Window gets focus | Event — onWindowFocusGained |
| Plugin signals loading is complete | Message — sendMessage<bool>(Loaded, true) |
| Per-frame animation | Event — onUpdate |
| Notify all subscribers that level changed | Message — sendMessage<LevelData>(...) |
| Custom input hardware event | Event — custom dispatcher + publish() |
// GameLogic.h — sends messages, never knows HUD exists
class GameLogic : public ml::ComponentWith
{
public:
void addScore(int points)
{
_score += points;
sendMessage(Event::ScoreChanged, _score);
if (_score >= 1000) sendMessage(Event::GameWon, true);
}
private:
int _score = 0;
};
// HUD.h — receives messages, never knows GameLogic exists
class HUD : public ml::ComponentWith
{
public:
HUD()
{
onMessage(GameManifest::Event::ScoreChanged, [this](const int& s) {
_scoreText.setString("Score: " + std::to_string(s));
});
onMessage(GameManifest::Event::GameWon, [this](const bool&) {
_winBanner.enableFlag(Flag::Visible);
});
}
};
Messenger API Reference
| Method | Description |
|---|---|
sendMessage<T>(Enum e, const T& data) | Send a typed message to all subscribers of this enum+type combination. |
onMessage<T>(Enum e, callback) | Register a callback to receive messages of this enum+type combination. |
offMessage<T>(Enum e) | Remove this object's subscription for the given enum+type combination. |
offAllMessages() | Remove all message subscriptions on this object. Called automatically on destruction. |
ml::MessageManager is accessible directly for code that does not inherit Messenger — use MessageManager::subscribe, ::publish, and ::unsubscribe with an explicit void* subscriber key.