ml::docs | Your First App Manifests Events Plugins Scenes Graphics Resources
← All Tutorials
Beginner 4 sections ~15 min C++17/20

States

States model mutually exclusive modes with transition callbacks. Declare them in your manifest and the framework wires the state machine in automatically.

Introduction

What Are States?

States represent the current mode of a component — one active value from a named set. Unlike flags (which are independent booleans that can all be true at once), only one state can be active at a time, and transitions fire optional enter/exit callbacks.

The Malena way: Use states when a component can be in one of several distinct modes, and behavior or appearance changes meaningfully between them. States enforce mutual exclusivity automatically — you can't be both Idle and Running at once.

1Core Concepts

Declaring States in a Manifest

Add a State enum to your manifest. The framework's template machinery detects it at compile time and wires the state machine in automatically. The initial state is the zero-value of the enum (first declared value).

Manifest with State enum cpp
class EnemyManifest : public ml::Manifest
{
public:
    // Only one of these is active at a time
    enum class State { Idle, Patrolling, Chasing, Attacking, Dead };
    enum class Flag  { Visible, Invulnerable };  // flags can combine freely
};
The enter callback is not fired during construction — it only fires on explicit transitions after initialization.

2Core Concepts

State API Reference

MethodDescription
setState(State::X)Transition to X. Fires exit callback on old, enter callback on new.
getState()Return the current StateEnum value.
isState(State::X)Return true if currently in state X.
onStateEnter(callback)Register a callback invoked after each transition. Receives new state.
onStateExit(callback)Register a callback invoked before each transition. Receives old state.
Full state API in use cpp
class Enemy : public ml::ComponentWith
{
public:
    void initialize()
    {
        // Wire transition callbacks
        onStateEnter([this](State s) {
            switch (s) {
                case State::Patrolling: startPatrolAnimation(); break;
                case State::Chasing:   startRunAnimation();    break;
                case State::Dead:      startDeathAnimation();  break;
                default: break;
            }
        });

        onStateExit([this](State s) {
            if (s == State::Attacking)
                cancelAttackHitbox();
        });

        setState(State::Patrolling);  // fires enter(Patrolling)
    }

    void update()
    {
        // Use isState() and getState() to drive logic
        if (isState(State::Chasing) && playerInAttackRange())
            setState(State::Attacking);
    }

    void takeDamage(int amount)
    {
        _health -= amount;
        if (_health <= 0 && !isState(State::Dead))
            setState(State::Dead);  // fires exit(current), enter(Dead)
    }
};

3Core Concepts

Multiple States via Traits

When you mix traits into a component via ComponentWith, any trait that has its own State enum contributes a separate state machine. GatherStates aggregates all of them, and a single setState() overloaded on enum type handles all of them.

Multiple independent state machines cpp
// DraggableManifest::State has: { FREE, LOCK_X, LOCK_Y }
// MyManifest::State has:        { Idle, Active, Done }

class MyWidget : public ml::ComponentWith {};

MyWidget w;

// Both state machines are completely independent
w.setState(MyManifest::State::Active);              // widget state machine
w.setState(ml::DraggableManifest::State::LOCK_Y);  // drag axis state machine

// Each has its own callbacks
w.onStateEnter([](MyManifest::State s) {
    // fires only for MyManifest::State transitions
});
Setting MyManifest::State::Active has zero effect on DraggableManifest::State, and vice versa. Each state machine is entirely independent.