ml::docs | Your First App Manifests Events Messaging Plugins Scenes Resources
See all tutorials →
Intermediate 5 sections ~15 min C++17/20

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.

Introduction

Two Communication Systems

Malena has two complementary communication systems. Knowing which to use is the key.

Event SystemMessage System
Key typeString ("click", "hover")User-defined enum
PayloadOptional sf::EventAny typed value (int, struct, etc.)
Who fires itUIManager (input) or dispatchersYour application code
Best forInput, hover, focus, per-frame updateApp-level signals between objects
TraitMessenger / SubscribableMessenger

Use the message system when you want one object to tell another object something happened — without the two objects knowing about each other directly.


1Core Concepts

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.

Declare your message enum cpp
// GameEvents.h — shared between sender and receiver
enum class GameEvent { Start, Stop, ScoreChanged, LevelUp };
Sender — any component, plugin, or app code cpp
// 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});
Receiver — set up in onReady() or onLoad() cpp
// 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);
});
The message key is a 3-part key: enum type + enum value + payload type. A receiver for <int>(GameEvent::ScoreChanged) will never accidentally receive a <float>(GameEvent::ScoreChanged) message — they are different subscriptions.

2Core Concepts

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.

HUDManifest.h — Event enum in manifest cpp
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 };
};
HUD.h — using manifest Event enum cpp
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();
        });
    }
};
Adding 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.

3Core Concepts

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.

Manual unsubscribe cpp
// Remove one specific subscription
offMessage(GameEvent::ScoreChanged);

// Remove all message subscriptions on this object
offAllMessages();
It is safe to call offMessage or sendMessage from inside a message callback. The MessageManager defers removal until after the current delivery completes, preventing iterator-invalidation crashes.

4Core Concepts

Messages vs Events — When to Use Each

Choose based on who fires it and what it carries.

ScenarioUse
Player clicks a buttonEvent — onClick
Score increases by 10Message — sendMessage<int>(ScoreChanged, 10)
Window gets focusEvent — onWindowFocusGained
Plugin signals loading is completeMessage — sendMessage<bool>(Loaded, true)
Per-frame animationEvent — onUpdate
Notify all subscribers that level changedMessage — sendMessage<LevelData>(...)
Custom input hardware eventEvent — custom dispatcher + publish()
Full example — score system without direct references cpp
// 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);
        });
    }
};

5Reference

Messenger API Reference

MethodDescription
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.
The underlying ml::MessageManager is accessible directly for code that does not inherit Messenger — use MessageManager::subscribe, ::publish, and ::unsubscribe with an explicit void* subscriber key.