The fsm
provides an easy way to manage finite state machines, almost without overhead.
This library requires C++20 and is based on meson build system.
To run unit tests:
$ meson build
$ ninja -C build test
To generate the single-include header, quom is required:
$ source scripts/amalgamate.sh
Every state and every handled event is simply a type, with only two mandatory requirements:
- a state must provide a public alias,
transitions
, that is actfsm::type_map
ofevent
s and their relative target states, basically the edges list of a graph, events must be unique; - a state must be defaultly constructible.
And can, optionally:
- expose a
void on_enter()
function, that will be called every time the fsm enters in this state; optionally it can receive the event instance:void on_enter(event& e)
; - expose a
void on_exit()
function, that will be called every time the fsm exits from this state, optionally it can receive the event instance:void on_exit(event& e)
;
struct on;
struct off;
struct switch_toggle {};
struct blackout {}
struct on
{
using transitions = ctfsm::type_map<
std::pair<switch_toggle, off>,
std::pair<blackout, off>
>;
void on_exit(blackout& event)
{
...
}
void on_exit()
{
...
}
};
struct off
{
using transitions = ctfsm::type_map<
std::pair<switch_toggle, on>
>;
void on_enter()
{
...
}
};
In this case:
on
handles the event triggered when changing state fromon
with theblackout
event with a different handler than other exit events;off
handles everyon_enter
event;
While switch_toggle
and blackout
are valid events, it can be useful to include data and event handlers in them:
struct switch_toggle
{
int data1;
std::string data2;
void on_transit()
{
...
}
};
switch_toggle
provides a payload that will be accessible to states in their event handlers. The on_transit event handler will be triggered every time the state machine handles an event of this type.
Finally, to build an fsm
from these states, doing:
ctfsm::fsm<on> state_machine;
is enough, all reachable states will be deduced from the provided initial state and on
will be the starting state.
Every state is default-constructed when the fsm is constructed. Their duration is exactly the same of the fsm that owns them.
Given an event instance,
event t;
to trigger a state change in the fsm:
state_machine.handle_event(t);
or
state_machine(t);
This call will trigger, in this order:
current_state.on_exit(event&)
if available,.on_exit()
otherwise;t.on_transit()
if available;next_state.on_enter(event&)
if available,.on_enter()
otherwise;
If the event is default initializable, it is possible to:
state_machine.handle_event<event>();
Sometimes, it is necessary to invoke a method on the current state to update its data members or apply specific application logic. The invoke_on_current function is provided to facilitate this type of operation.
fsm.invoke_on_current([](auto& current_state, auto& fsm) {
current_state.work(fsm);
});
This example implies that every state of fsm
provides a function .work
that takes a reference to the fsm itself.
This allows to update the fsm state inside an invoke_on_current
execution. The operation sequence in this case is:
invoke_on_current
is invoked;- inside it, an
fsm.handle_event
is triggered; on_exit
of the current state is invoked, if present;on_transit
of the event is invoked, if present;on_enter
of the next state is invoked, if present;invoke_on_current
execution continues.
Function | Description |
---|---|
constexpr bool handle_event(event) |
This function is enabled only if exceptions are disabled with -fno-exceptions flag The event is delivered to states and the current state is updated if a valid transaction for this event is found. If this event cannot be handled by the current state, the function returns false . |
constexpr void handle_event(event) |
This function is enabled only if exceptions are enabled The event is delivered to states and the current state is updated if a valid transaction for this event is found. If this event cannot be handled by the current state, an std::runtime_error is thrown. |
constexpr bool handle_event<event_t>() |
This function is enabled only if exceptions are disabled with -fno-exceptions flag The event of type event_t is default-constructed and delivered to states and the current state is updated if a valid transaction for this event is found. If this event cannot be handled by the current state, the function returns false . |
constexpr void handle_event<event_t>() |
This function is enabled only if exceptions are enabled The event of type event_t is default-constructed and delivered to states and the current state is updated if a valid transaction for this event is found. If this event cannot be handled by the current state, an std::runtime_error is thrown. |
constexpr const id_type& get_current_state_id() const noexcept |
The ID of the current state is returned: if every state has a member named id of the same type, the id of the current state is returned, otherwise typeid(current_state_t).name() is returned. |
constexpr bool is_current_state<T>() const noexcept |
Returns true if T is the type of the current state. |
constexpr auto invoke_on_current(auto lambda) noexcept |
Lambda must be invocable with a reference to the current state instance and the fsm itself. The value returned by lambda is forwarded to the caller. |
- A state class must provide a
transitions
alias, actfs::type_map
composed bystd::pair
s where the first type is an event and the second type is the target state after that event is triggered. If notransitions
alias is provided, the state will be a sink state. - Each state can provide a publicly accessible
id
field. The library will use this field only if every state of the FSM provides anid
field of the same type. - A state can handle
exit
events, which are triggered when the FSM changes state while it is the current state. An unlimited number ofvoid on_exit(E& event)
overloads and avoid on_exit()
method can be provided. The no-argument handler will be invoked only for events that do not have a specific overload. A state withoutexit
handlers is also completely valid. - A state can handle
enter
events, which are triggered when the FSM changes state while it is the target state. An unlimited number ofvoid on_enter(E& event)
overloads and avoid on_enter()
method can be provided. The no-argument handler will be invoked only for events that do not have a specific overload. A state withoutenter
handlers is also completely valid.