All code examples include boost/sml.hpp as well as declare a convienent sml namespace alias.

#include <boost/sml.hpp>
namespace sml = boost::sml;

0. Read Boost.MSM - eUML documentation

1. Create events and states

State machine is composed of finite number of states and transitions which are triggered via events.

An Event is just a unique type, which will be processed by the state machine.

struct my_event { ... };

You can also create event instance in order to simplify transition table notation.

auto event = sml::event<my_event>;

If you happen to have a Clang/GCC compiler, you can create an Event on the fly.

using namespace sml;
auto event  = "event"_e;

However, such event will not store any data.


A State can have entry/exit behaviour executed whenever machine enters/leaves State and represents current location of the state machine flow.

To create a state below snippet might be used.

auto idle = sml::state<class idle>;

If you happen to have a Clang/GCC compiler, you can create a State on the fly.

using namespace sml;
auto state  = "idle"_s;

However, please notice that above solution is a non-standard extension for Clang/GCC.

SML states cannot have data as data is injected directly into guards/actions instead.

A state machine might be a State itself.

sml::state<state_machine> composite;

SML supports terminate state, which stops events to be processed. It defined by X.

"state"_s = X;

States are printable too.

assert(string("idle") == "idle"_s.c_str());

CPP(BTN) CPP(BTN) CPP(BTN)

 


2. Create guards and actions

Guards and actions are callable objects which will be executed by the state machine in order to verify whether a transition, followed by an action should take place.

Guard MUST return boolean value.

auto guard1 = [] {
  return true;
};

auto guard2 = [](int, double) { // guard with dependencies
  return true;
};

auto guard3 = [](int, auto event, double) { // guard with an event and dependencies
  return true;
};

struct guard4 {
    bool operator()() const noexcept {
        return true;
    }
};

Action MUST not return.

auto action1 = [] { };
auto action2 = [](int, double) { }; // action with dependencies
auto action3 = [](int, auto event, double) { }; // action with an event and dependencies
struct action4 {
    void operator()() noexcept { }
};

CPP(BTN)

 


3. Create a transition table

When we have states and events handy we can finally create a transition table which represents our transitions.

SML is using eUML-like DSL in order to be as close as possible to UML design.

  • Transition Table DSL

    • Postfix Notation
    Expression Description
    state + event [ guard ] internal transition on event e when guard
    src_state / [] {} = dst_state anonymous transition with action
    src_state / [] {} = src_state self transition (calls on_exit/on_entry)
    src_state + event = dst_state external transition on event e without guard or action
    src_state + event [ guard ] / action = dst_state transition from src_state to dst_state on event e with guard and action
    src_state + event [ guard && (![]{return true;} && guard2) ] / (action, action2, []{}) = dst_state transition from src_state to dst_state on event e with guard and action
    • Prefix Notation
    Expression Description
    state + event [ guard ] internal transition on event e when guard
    dst_state <= src_state / [] {} anonymous transition with action
    src_state <= src_state / [] {} self transition (calls on_exit/on_entry)
    dst_state <= src_state + event external transition on event e without guard or action
    dst_state <= src_state + event [ guard ] / action transition from src_state to dst_state on event e with guard and action
    dst_state <= src_state + event [ guard && (![]{return true;} && guard2) ] / (action, action2, []{}) transition from src_state to dst_state on event e with guard and action
  • Transition flow

src_state + event [ guard ] / action = dst_state
                                     ^
                                     |
                                     |
                                    1. src_state + on_exit
                                    2. dst_state + on_entry

To create a transition table make_transition_table is provided.

using namespace sml; // Postfix Notation

make_transition_table(
 *"src_state"_s + event<my_event> [ guard ] / action = "dst_state"_s
, "dst_state"_s + "other_event"_e = X
);

or

using namespace sml; // Prefix Notation

make_transition_table(
  "dst_state"_s <= *"src_state"_s + event<my_event> [ guard ] / action
, X             <= "dst_state"_s  + "other_event"_e
);

CPP(BTN) CPP(BTN)

 


4. Set initial states

Initial state tells the state machine where to start. It can be set by prefixing a State with *.

using namespace sml;
make_transition_table(
 *"src_state"_s + event<my_event> [ guard ] / action = "dst_state"_s,
  "dst_state"_s + event<game_over> = X
);

Initial/Current state might be remembered by the State Machine so that whenever it will reentered the last active state will reactivated. In order to enable history you just have to replace * with postfixed (H) when declaring the initial state.

using namespace sml;
make_transition_table(
  "src_state"_s(H) + event<my_event> [ guard ] / action = "dst_state"_s,
  "dst_state"_s    + event<game_over>                   = X
);

You can have more than one initial state. All initial states will be executed in pseudo-parallel way . Such states are called Orthogonal regions.

using namespace sml;
make_transition_table(
 *"region_1"_s   + event<my_event1> [ guard ] / action = "dst_state1"_s,
  "dst_state1"_s + event<game_over> = X,

 *"region_2"_s   + event<my_event2> [ guard ] / action = "dst_state2"_s,
  "dst_state2"_s + event<game_over> = X
);

CPP(BTN) CPP(BTN)

 


5. Create a state machine

State machine is an abstraction for transition table holding current states and processing events. To create a state machine, we have to add a transition table.

class example {
public:
  auto operator()() {
    using namespace sml;
    return make_transition_table(
     *"src_state"_s + event<my_event> [ guard ] / action = "dst_state"_s,
      "dst_state"_s + event<game_over> = X
    );
  }
};

Having transition table configured we can create a state machine.

sml::sm<example> sm;

State machine constructor provides required dependencies for actions and guards.

                            /---- event (injected from process_event)
                            |
auto guard = [](double d, auto event) { return true; }
                   |
                   \--------\
                            |
auto action = [](int i){}   |
                  |         |
                  |         |
                  \-\   /---/
                    |   |
sml::sm<example> s{42, 87.0};

sml::sm<example> s{87.0, 42}; // order in which parameters have to passed is not specificied

Passing and maintaining a lot of dependencies might be tedious and requires huge amount of boilerplate code. In order to avoid it, Dependency Injection Library might be used to automate this process. For example, we can use ext Boost.DI.

auto injector = di::make_injector(
    di::bind<>.to(42)
  , di::bind<interface>.to<implementation>()
);

auto sm = injector.create<sm<example>>();
sm.process_event(e1{});

CPP(BTN) CPP(BTN)

 


6. Process events

State machine is a simple creature. Its main purpose is to process events. In order to do it, process_event method might be used.

sml::sm<example> sm;

sm.process_event(my_event{}); // handled
sm.process_event(int{}); // not handled -> unexpected_event<int>

Process event might be also triggered on transition table.

using namespace sml;
return make_transition_table(
 *"s1"_s + event<my_event> / process(other_event{}) = "s2"_s,
  "s2"_s + event<other_event> = X
);

SML also provides a way to dispatch dynamically created events into the state machine.

struct game_over {
  static constexpr auto id = SDL_QUIT;
  // explicit game_over(const SDL_Event&) noexcept; // optional, when defined runtime event will be passed
};
enum SDL_EventType { SDL_FIRSTEVENT = 0, SDL_QUIT, SDL_KEYUP, SDL_MOUSEBUTTONUP, SDL_LASTEVENT };
//create dispatcher from state machine and range of events
auto dispatch_event = sml::utility::make_dispatch_table<SDL_Event, SDL_FIRSTEVENT, SDL_LASTEVENT>(sm);
SDL_Event event{SDL_QUIT};
dispatch_event(event, event.type); // will call sm.process(game_over{});

CPP(BTN) CPP(BTN) CPP(BTN)

 


8. Handle errors

In case when a State Machine can't handle given event an unexpected_event is fired.

make_transition_table(
 *"src_state"_s + event<my_event> [ guard ] / action = "dst_state"_s
, "src_state"_s + unexpected_event<some_event> = X
);

Any unexpected event might be handled too by using unexpected_event<_>.

make_transition_table(
 *"src_state"_s + event<my_event> [ guard ] / action = "dst_state"_s
, "src_state"_s + unexpected_event<some_event> / [] { std::cout << "unexpected 'some_event' << '\n'; "}
, "src_state"_s + unexpected_event<_> = X // any event
);

In such case...

sm.process_event(some_event{}); // "unexpected 'some_event'
sm.process_event(int{}); // terminate
assert(sm.is(X));

Usually, it's handy to create additional Orthogonal region to cover this scenario, This way State causing unexpected event does not matter.

make_transition_table(
 *"idle"_s + event<my_event> [ guard ] / action = "s1"_s
, "s1"_s + event<other_event> [ guard ] / action = "s2"_s
, "s2"_s + event<yet_another_event> [ guard ] / action = X
// terminate (=X) the Machine or reset to another state
,*"error_handler"_s + unexpected_event<some_event> = X
);

We can always check whether a State Machine is in terminate state by.

assert(sm.is(sml::X)); // doesn't matter how many regions there are

When exceptions are enabled (project is NOT compiled with -fno-exceptions) they can be caught using exception<name> syntax. Exception handlers will be processed in the order they were defined, and exception<> might be used to catch anything (equivalent to catch (...)). Please, notice that when there is no exception handler defined in the Transition Table, exception will not be handled by the State Machine.

make_transition_table(
 *"idle"_s + event<event> / [] { throw std::runtime_error{"error"}; }
,*"error_handler"_s + exception<std::runtime_error> = X
, "error_handler"_s + exception<std::logic_error> = X
, "error_handler"_s + exception<> / [] { cleanup...; } = X // any exception
);

CPP(BTN)

 


9. Test it

Sometimes it's useful to verify whether a state machine is in a specific state, for example, if we are in a terminate state or not. We can do it with SML using is or visit_current_states functionality.

sml::sm<example> sm;
sm.process_event(my_event{});
assert(sm.is(X)); // is(X, s1, ...) when you have orthogonal regions

//or

sm.visit_current_states([](auto state) { std::cout << state.c_str() << std::endl; });

On top of that, SML provides testing facilities to check state machine as a whole. set_current_states method is available from testing::sm in order to set state machine in a requested state.

sml::sm<example, sml::testing> sm{fake_data...};
sm.set_current_states("s3"_s); // set_current_states("s3"_s, "s1"_s, ...) for orthogonal regions
sm.process_event(event{});
assert(sm.is(X));

CPP(BTN)

 


10. Debug it

SML provides logging capabilities in order to inspect state machine flow. To enable logging you can use (Logger Policy)(user_guide.md#policies)

struct my_logger {
  template <class SM, class TEvent>
  void log_process_event(const TEvent&) {
    printf("[%s][process_event] %s\n", sml::aux::get_type_name<SM>(), sml::aux::get_type_name<TEvent>());
  }

  template <class SM, class TGuard, class TEvent>
  void log_guard(const TGuard&, const TEvent&, bool result) {
    printf("[%s][guard] %s %s %s\n", sml::aux::get_type_name<SM>(), sml::aux::get_type_name<TGuard>(),
           sml::aux::get_type_name<TEvent>(), (result ? "[OK]" : "[Reject]"));
  }

  template <class SM, class TAction, class TEvent>
  void log_action(const TAction&, const TEvent&) {
    printf("[%s][action] %s %s\n", sml::aux::get_type_name<SM>(), sml::aux::get_type_name<TAction>(),
           sml::aux::get_type_name<TEvent>());
  }

  template <class SM, class TSrcState, class TDstState>
  void log_state_change(const TSrcState& src, const TDstState& dst) {
    printf("[%s][transition] %s -> %s\n", sml::aux::get_type_name<SM>(), src.c_str(), dst.c_str());
  }
};

my_logger logger;
sml::sm<logging, sml::logger<my_logger>> sm{logger};
sm.process_event(my_event{}); // will call logger appropriately

CPP(BTN) CPP(BTN)