extended meta finite state automaton
xmsm (eXtended Meta State Machine) is a header-only C++23/26 library for implementing advanced state machines with support for state stacks, inter-scenario interactions, and multiple instances. It focuses on performance (static polymorphism, constexpr) and flexibility (metaprogramming), making it ideal for complex systems with minimal runtime overhead.
- State Stack: Ability to preserve previous states and return to them.
- Single and Multi Scenarios: Supports both classic state machines (
single_scenario) and collections of identical machines (multi_scenario). - Scenario Interaction: Transitions can depend on or affect other scenarios via modifiers (
when,only_if,move_to,try_move_to). Also an exprssion can to be used (for examplein<scenario, state1, state2>(f) && now_in<scenario2, state3>(f)) - Compile-Time Logic: Most computations (states, events, transitions) are performed at compile time.
- Flexible Customization: User-defined factories allow tailoring the library to any needs.
- A modern compiler with C++23 support (e.g., GCC 14+, Clang 20+). Some features leverage C++26 (optional).
- Minimal dependencies – only the C++ Standard Library is required.
xmsm is a header-only library, so installation is straightforward:
- Clone the repository:
git clone https://github.com/zerhud/xmsm.git
- Use
make installin root of the project or copy the header files from theincludedirectory to a location accessible to your compiler (e.g.,/usr/local/include/or your project):cp xmsm.hpp /usr/local/include/ cp -r xmsm /usr/local/include/
- For Nix users: the repository contains
flake.nixfile, so you can usenix developwith it
You can also compile tests. It requires g++ and clang++ with c++23 support. The make command will build all tests (for example make -j$(nproc --all)).
To start using:
- implement a factory. the factory is a simple class without data. it allows to separte realization from the idea
- defining scenarios
- declare machine with scenarios. the machine requires factory and scenarios
template<typename factory> usign machine = xmsm::machine<factory, scenario1, scenario2, scenario3>;it allows to use machine with any other factoryusing machine = xmsm::machine<implemented_factory, scenaro1, scenario2, scenario3>;for use with concrete factory.
There is example in tests/example.cpp
The factory is a class with realisation. It allows user to separate type of dependencies and it's creation from inner logic.
For example, with C++ standard library
struct factory {
using string_view = std::string_view; // will be removed soon
};
// tag dispatching is used for create objects. tags declared in xmsm/declarations.hpp
// containers and erase method for it (can to be implemented for each container separately)
template<typename type> constexpr auto make(struct tags::vector, const std_factory&, _type_c<type>) {
return std::vector<type>{};
}
template<typename type> constexpr auto make(struct tags::list, const std_factory&, _type_c<type>) {
return std::list<type>{};
}
constexpr void erase(const std_factory&, auto& cnt, auto ind) {
cnt.erase(cnt.begin() + ind);
}
// variant to store states
template<typename... types> constexpr auto make(struct tags::variant, const std_factory&, type_list<types...>) {
return std::variant<types...>{};
}
// mk_atomic allows to use xmsm with threads.
// it can to be implemented like this if only thread will be used, other way use std::atomic<type>{};
// this method requires only with multi scenario
template<typename type> constexpr auto make(struct tags::atomic, const std_factory&, type_list<type>) {
return type{};
}
// also the method can to be implemented to handle exceptions in state creation and switch events
// or can to be omited. the method will be called from catch block, if exists.
template<typename scenario, typename trans, typename next> constexpr void on_exception(const factory& f) {
throw;
}Also, you can use predefined factory for std library
Scenario is a class with static auto describe_sm(const auto& f) method. The method returns machine description. The parameter used for methods in ADL.
For example:
struct my_object {
int value{0}; // any data you want, or no fields at all
static auto describe_sm(const auto& f) {
return mk_sm_description(f // the f object is used for ADL
, mk_trans<state<0>, state<1>, event<0>>(f) // Transition from state<0> to state<1> on event<0>
, mk_trans<state<1>, state<0>, event<1>>(f)
, pick_def_state<state<0>>(f) // Default state (if not present the first, e.g. state<0> will be used)
);
}
};the mk_multi_sm_description should be used for create a scenario with few sm inside.
the mk_trans methods describe a transition and has few template parameters:
- state from (can to be void)
- state to
- event (optional)
also it accepts any parameters - modifiers. some modifiers can to be passed to mk_sm_description.
Transitions can be customized with modifiers:
when: Triggers a transition if a condition on other scenarios is true. the condition will be describe later.mk_trans<state<0>, state<1>>(f, when(f, in<other_scenario1, state<1>>(f) && now_in<other_scenario2, state<2>>(f)))
only_if: Blocks a transition unless a condition is met.mk_trans<state<0>, state<1>>(f, only_if(f, in<other_scenario1, state<1>>(f)))
stack_by_event: Pushes the current state onto the stack, with return triggered by specified events. The current state of the scenario will be the second state in the transition description.if few frames exist and the "back event" occurred, all stack frames with the same "back event" will be destroyed// to to state<1> and save state<0> on previus stack frame mk_trans<state<0>, state<1>, event<0>>(f, stack_by_event<event<10>>(f))
stack_by_expr: Same asstack_by_eventbut expression is used to destroy framemk_trans<state<0>, state<1>, event<0>>(f, stack_by_expr(f, now_in<other_scenario1, state<1>>(f)))
move_to: Attempts to move another scenario to a specified state. If it fails, move the scenario to fail state- if the required state cannot to be achived right now the scenario will track a path to it state and if scenario makes "wront turn" the fail state will be setted.
- tansitions in other scenario have to be marked with
allow_queuefor to be trackable - transitions in other scenario can to be marked with
allow_move. the mark allows transition happen onmove_to.
try_move_to: Same asmove_to, but dose nothing on fail
there is also few modifiers for use in mk_sm_description:
to_state_modsfor add some modifiers to all transitions where the to state is same as in the methodfrom_state_modsfor add some modifiers to all transitions where the from state is same as in the methodpick_def_stateallow to set default state for scenario. if there is no such method the first state will be used as defaultentityallows to specify an entity for scenario. it allows to use the xmsm in different processes.- also few modifiers onyl for multi scenario
start_eventthe scenario will start on the eventfinish_statethe scenario will be destroied when achive this state
some modifiers works with conditions. any conditions can to be combined with logical operators - for example in<scenario, state>(f) && affected<scenario>(f).
in<scenario, state1, state2, state3>(f)true when scenario in one of listed statesaffected<scenario1, scenario2>(f)true when all of listed scenario was changed in current eventcnt_infor multi scenario, true when count of scenarios achives required statebrokentrue when scenario is in broken state - for example due exceptionnow_inis the same asinandaffected
xmsm is distributed under the GNU Affero General Public License. See the COPYING file in the repository root.
Contributions are welcome! Fork the repo, create a PR, or open an issue on GitHub.