ZFSM
Header-only C++ library for hierarchical finite state machines with modularity and reusability
ZFSM source code and docs

Authorship

ZFSM and this Website were created and are maintained by Michael Baldamus (https://linkedin.com/in/michael-baldamus) under the legal disclaimers shown below.

"Transitions across state boundaries considered harmful" or what ZFSM is all about

Code patterns and graphical tools for state machines are widely used and often support states that are contained in each other or, equivalently, state hierarchies. Most often, such approaches permit arrows (also known as transitions) across state boundaries. One of the simplest examples of that would be two states a and b and an arrow drawn from a to a substate c of b. Now, does not everybody (largely) believe in Dijkstra's famous dictum, "Go To Statement Considered Harmful"? ZFSM has its starting point in the observation that arrows across state boundaries are nothing but the equivalent of goto's in the state machine realm. In the example, the arrow from a to c breaks encapsulation present in b because it depends on knowing that b is a superstate and that c is one of its substates.

ZFSM provides a framework for hierarchical state machines. While adopting an object-oriented approach where hierarchies can be expressed in terms of object composition, it also provides means of supplanting arrows going across state boundaries. In synergy, these two main features provide excellent support for keeping state machines internally modular and their components reusable. Many practical applications of hierarchical state machines consist of deep hierarchies and hundreds if not thousands of states, together with the necessity of supporting different variants thereof. By way of focusing on modularity and reusablity, ZFSM should be particularly useful in such settings.

ZFSM state machines are not (variants of) UML state machines or, to use more precise terminology, UML Statecharts. I found that the objectives pursued by ZFSM are more compatible with the SyncChart approach to state machines, so it is SyncCharts that ZFSM adapts for its purposes.

Source code

Download Version 0.1.4 of the ZFSM library at https://mbaldamus.com/zfsm/zfsm.hpp.

Technical overview

ZFSM is a C++ library for hierarchical finite state machines. (The 'Z' in 'ZFSM' stands for 'ultimate' since 'Z' is the last letter in the English alphabet.) Its main features are as follows:

  • Header-only.
  • Small footprint, currently less than 1500 lines of code.
  • Adhering to C++14 as implemented in GCC and Clang.
  • Six types of states are supported:
    1. Top states.
    2. Superstates.
    3. Composite states which consist of concurrent regions.
    4. Basic states.
    5. Initial and final states.
  • Six types of arrows (transitions) are supported:
    1. React.
    2. Continue.
    3. (Immediate) Abort.
    4. (Immediate) Suspend.
  • Besides user-specified payloads associated with any type of arrow, payloads can also be associated with hierarchical states and concurrent regions. There are four execution disciplines for such payloads:
    1. On state or region entry or exit.
    2. Prior or after any execution sequence within the state or region.
  • Strong emphasis on state modularity by forbidding arrows that cross state or region boundaries.
  • A signaling mechanism that supplants boundary-crossing arrows while being compatible with modularity besides serving other purposes.
  • State hierarchies can be expressed naturally in terms of object composition.
  • Supporting modular reuse of hierarchical states and regions by expressing them as class instances.
  • Incurring heap usage only when state machines are constructed, while no heap usage is incurred later on.
  • Using callables, normally lambdas, as arrow guards and payloads, as well as using callables as action payloads.
  • Mix-in architecture via which states and regions only ever instantiate the resources they need for the specific purpose at hand.
  • Deterministic, i.e. scheduling-free execution of concurrent regions.
  • Neither virtual inheritance nor RTTI used in implementing all this functionality.
  • Configurable error handling.
  • Free and open source, generous Apache 2.0 license.
  • Adapting the SyncChart approach to hierarchical finite state machines.

A word on ZFSM and AI

As a state machine library, ZFSM takes the approach of designing state machines directly at the programming language level. Compare that to employing some dedicated framework, which would usually come in the form of some kind of GUI environment with its own file formats. GUI approaches may appear more user-friendly on the face of it, but the classical advantage of working with a library is being able to combine state machine patterns with the given language's and its ecosystem's full power in terms of complementary programming patterns and tool support. The point is that the age of AI brings another advantage, which is that dedicated frameworks, be they solely state-machine oriented or even more general like UML, will always have far less training data accessible to AI than any general programming language ecosystem. — With such ecosystems, AI constantly ingests and improves itself on gargantuan and continually growing amounts of all kinds of code accessible in all kinds of repositories. Hence the programming language approach to designing state machines, as opposed to utilizing any more restricted framework, should always find better overall support by AI.

Basic examples

Hello-world example

Here is a most basic hello-world example of using ZFSM.

  #include <cassert>
  #include <iostream>
  #include "zfsm.hpp"

  class SayHello : public zfsm::Top<>
  {
    zfsm::Init<zfsm::REACT> init{this};
    zfsm::Final final{this};

    zfsm::React<> init_2_final{init, final, []{ std::cout << "ZFSM is saying hello!" << std::endl; }};

  public: 
    SayHello() : zfsm::Top<>(init) { fuse(); }
  };

  int main()
  {
    SayHello example;
    assert (!example.tick());
    return 0;
  }

This example features a state machine type, SayHello, whose instances have two substates, init and final, and one react arrow, init_2_final, from init to final. This arrow has no guard and a payload, which writes 'ZFSM is saying hello!' to stdout. The arrow is triggered by calling a member function, tick(), on an instance, sayHello, of SayHello. The state machine terminates by executing the arrow, whence tick() returns false.

Please note that all user-level elements of ZFSM live in a name space zfsm, a property we will not mention in the remainder of this section anymore.

Going back to the example, SayHello inherits from a template class Top, here instantiated with an empty parameter list because SayHello does not require any non-default capabilities. — Sub-states and arrows as class members is a default capability of top states and, thus, does not have to be specified in the parameter list. States init and final are members of SayHello. Both have to be contextualized with the top state by putting this as an initialization parameter. They are instances of classes Init and Final, respectively, where Init has to be provided with a capability of REACT because a react arrow is attached to init. This arrow is another member of SayHello. It is initialized with its source state, init, its target state, final, and a payload action in the form of a lambda. Constructing SayHello requires initializing Top with init to tell it which substate is the initial one. Moreover, it requires calling a post-construction member function fuse(). This call has to be issued once per top state.

Please note how this example shows that ZFSM supports expressing relationships between superstates and substates in terms of object composition. It needs be pointed out, too, that internal arrows, i.e. arrows between direct substates, do not appear as member functions, as would be the case according to more classical state machine patterns. All arrows are data and internal arrows can be instance members just like substates.

a, b, and c examples

Basic a, b, and c example

The basic a, b, and c example has a as the initial state of its top state, b as a superstate within the top state, and c as a nested state within b. Instead of drawing a direct arrow from a to c, a React arrow is drawn from a to b together with drawing a Continue arrow from b's initial state to c. These two arrows in combination entail that the transition from a to b with c as its internal state is made within one tick while, at the same time, avoiding any boundary-crossing arrow or, in other words, affording b a clean, unbroken interface. There are other state machine libraries and tools that support this pattern or similar ones, even if boundary-crossing arrows are supported in principle, but we will explain what the real point is when it comes to next example. For the moment, please note how the basic a, b, and c example already shows how ZFSM supports mapping state hierarchies one-to-one to class syntax, here in the form of B appearing as a nested class inside of ABC.

  #include <cassert>
  #include <iostream>
  #include "zfsm.hpp"

  using namespace zfsm;

  class ABC : public Top<>
  {
    Init<REACT> a{this};

    class B : public Super<CONTINUE>
    {
      Init<CONTINUE> init{this};
      Basic<REACT> c{this};
      Final final{this};

      Continue<> init_2_c{init, c, []{ std::cout << "Basic A-B-C test." << std::endl; }};
      React<> c_2_final{c, final};

    public:
      B(ABC *abc) : Super<CONTINUE>(abc, init) {}
    }
    b{this};

    Final final{this};

    React<> a_2_b{a, b};
    Continue<> b_2_final{b, final};

  public:
    ABC() : Top<>(a) { fuse(); }
  };

  int main()
  {
    ABC example;
    assert (example.tick());
    assert (!example.tick());
    return 0;
  }

More advanced a, b, and c example

Some of the most common patterns involving boundary-crossing arrows use fork transitions, that is to say, transitions that fork into different concurrent regions of a composite state while, at the same time, bypassing any initial states in these regions to target follow-on states directly. This kind of pattern is complex and, what is more, it breaks all encapsulation present in the composite state. — Fork transitions totally depend on open access to and knowledge about the composite state's sub-hierarchy, at least up to and including its own regions.

ZFSM proposes foregoing any such patterns, in this specific case supplanting them by combining React and guarded Continue transitions, where signals are used to guide control flow across state boundaries while, at the same time, affording the composite state complete encapsulation in the sense that its environment actually has no way of "enforcing" it to be composite. In other words, the encapsulation is such that the composite state can be supplanted by any other state or state hierarchy of the same external behavior as long as the signaling interface stays the same.

To focus soleley on the point just made, the more advanced a, b, and c example is very simple in almost all other respects. It does, however, exhibit a reuse pattern by introducing two free-standing region classes, R and S, with substates c1 and c2 or d1 and d2, respectively. These classes are instantiated inside of b, which is now composite. "Forking" into b is a consequence of immediate follow-on states on entering b being determined dynamically. Another framework might use two different fork transitions, one targeting c1 and d1, the other one targeting c2 and d2. By contrast, the modular approach proposed here introduces a value-carrying signal sig used in guarding Continue arrows targeting the four substates. The net effect, as advertised, is that b presents an interface just consisting of its ability to receive sig.

  #include <cassert>
  #include <iostream>
  #include "zfsm.hpp"

  using namespace zfsm;

  enum class Token {T1, T2};

  class R : public Region<>
  {
    Signal<Token> &sig;

    Init<CONTINUE> init{this};
    Basic<REACT> c1{this};
    Basic<REACT> c2{this};
    Final final{this};

    Continue<> init_2_c1{init, [&]{ return sig() && (sig.getValue() == Token::T1); }, c1};
    React<> c1_2_final{c1, final, []{ std::cout << "Got Token::T1" << std::endl; }};

    Continue<> init_2_c2{init, [&]{ return sig() && (sig.getValue() == Token::T2); }, c2};
    React<> c2_2_final{c2, final, []{ std::cout << "Got Token::T2" << std::endl; }};

  public:
    template<class Ctxt>
    R(Ctxt *ctxt, Signal<Token> &s) : Region<>(ctxt, init), sig(s) {}
  };

  class S : public Region<>
  {
    Signal<Token> &sig;

    Init<CONTINUE> init{this};
    Basic<REACT> d1{this};
    Basic<REACT> d2{this};
    Final final{this};

    Continue<> init_2_d1{init, [&]{ return sig() && (sig.getValue() == Token::T1); }, d1};
    React<> d1_2_final{d1, final, []{ std::cout << "Received Token::T1" << std::endl; }};

    Continue<> init_2_cd{init, [&]{ return sig() && (sig.getValue() == Token::T2); }, d2};
    React<> d2_2_final{d2, final, []{ std::cout << "Received Token::T2" << std::endl; }};

  public:
    template<class Ctxt>
    S(Ctxt *ctxt, Signal<Token> &s) : Region<>(ctxt, init), sig(s) {}
  };

  class ABC : public Top<SIGNAL>
  {
  public:
    Signal<Token> sig{this};

  private:
    Init<REACT> a{this};        

    class B : public Composite<CONTINUE> 
    {
      R r;
      S s;

    public:
      B(ABC *ctxt, Signal<Token> &sig) : Composite<CONTINUE>(ctxt), r(this, sig), s(this, sig) {}
    }
    b{this, sig};

    Final final{this};

    React<> init_2_b{a, b};
    Continue<> b_2_final{b, final};

  public:
    ABC() : Top<SIGNAL>(a) { fuse(); }
  };

  int main()
  {
    ABC example;

    ASSERT(example.tick(example.sig.raise(Token::T1)));
    ASSERT(!example.tick());

    example.exit();

    ASSERT(example.tick(example.sig.raise(Token::T2)));
    ASSERT(!example.tick());

    return 0;
  }

The a, b, and c example continued

To dwell on modularity a little further, the following example shows how b can be supplanted by a non-composite state of exactly the same behaviour without changing anything around it. Boundary-crossing fork arrows would make replacing b in such a direct way impossible.

  #include <cassert>
  #include <iostream>
  #include "zfsm.hpp"

  using namespace zfsm;

  enum class Token {T1, T2};

  class ABC : public Top<SIGNAL>
  {
  public:
    Signal<Token> sig{this};

  private:
    Init<REACT> a{this};        

    class B : public Super<CONTINUE> 
    {
      Signal<Token> &sig;

      Token tok;

      Init<CONTINUE> init{this};
      Basic<REACT> c{this};
      Final final{this};

      Continue<Token> init_2_c{init, sig, c, [&](Token t){ tok = t; }};

      React<> c_2_final{c, final, [&]{ 
        switch (tok) {
          case Token::T1:
            std::cout << "Got Token::T1" << std::endl << "Received Token::T1" << std::endl;
            break;
          case Token::T2: 
            std::cout << "Got Token::T2" << std::endl << "Received Token::T2" << std::endl;
            break;
          default:
            assert (false);
        }      
      }};

    public:
        B(ABC *ctxt, Signal<Token> &s) : Super<CONTINUE>(ctxt, init), sig(s) {}
    }
    b{this, sig};

    Final final{this};

    React<> init_2_b{a, b};
    Continue<> b_2_final{b, final};

  public:
    ABC() : Top<SIGNAL>(a) { fuse(); }
  };

  int main()
  {
    ABC example;

    ASSERT(example.tick(example.sig.raise(Token::T1)));
    ASSERT(!example.tick());

    example.exit();

    ASSERT(example.tick(example.sig.raise(Token::T2)));
    ASSERT(!example.tick());

    return 0;
  }

Library reference

Basics

  • ZFSM provides a C++ programming framework for hierarchical finite state machines.
  • ZFSM is a header-only library contained in a file zfsm.hpp.
  • All use of the ZFSM library is governed by the Apache 2.0 license.
  • ZFSM adheres to C++14 as implemented in newer versions of GCC and Clang.
  • All user-level elements of ZFSM live in name space zfsm.
  • None of the user-level elements of ZFSM can be copied, moved, or assigned-to, that is to say, all of them can only be constructed in place either on the stack or the heap.
  • Once a state machine has been constructed, ZFSM in and of itself does not induce any heap allocations during state machine execution. Excecuting user-supplied callables, that is to say, user-supplied arrow guards and payloads can still induce heap allocations. (Such heap allocations can, of course, not be controlled at the state machine level.)
  • ZFSM is not thread-safe.

States and regions

Overview

  • The following table gives an overview of how to construct states and regions.
     
    Top<Capability...>(Init& init)A top state with a designated initial state within the context directly underneath
    Init<Capability...>(Context *context)An initial state within an enclosing context
    Final(Context *context)A final state within an enclosing context
    Basic<Capability...>(Context *context)A basic state within an enclosing context
    Super<Capability...>(Context *context, Init& init)A superstate within an enclosing context, together with a designated initial state within the context directly underneath
    Composite<Capability...>(Context *context)A composite state within an enclosing context
    Region<Capability...>(Context *context, Init& init)A region within an enclosing context, which must a composite state, together with a designated initial state within the context directly underneath
     
    ☞ Context is always an internal class template parameter. Actual constructor arguments need be instances of one of the user-level context class templates Top<Capability...>, Super<Capability...>, Composite<Capability...>, and Region<Capability...>, or of user-defined subclasses thereof.
    ☞ Likewise, Init is always an internal class template parameter. Actual constructor arguments need be instances of the user-level class template, Init<Capability...>. It is an error if an initial state is used as a constructor argument for a context within which it is not placed by virtue of its own construction.
    ☞ Constructing a top state must be accompanied by calling member function fuse() on it exactly once.
    ☞ ZFSM does not perfom any lifetime management of state machine components.

Capabilities

  • Capabilities are enum constants used configure states and regions with the resources they need to carry signals, arrows, and/or actions. The following table shows which capabilities exist and which signal, arrow, and action types they enable.
     
    Signal-relatedArrow-relatedAction-related
    Capability:SIGNALREACTCONTINUEPREEMPTIMMEDIATE_PREEMPTON_ENTRYON_EXITDO_FRONTDO_BACK
    Types(s) enabled:SignalReactConnect
    Abort
    Suspend
    ImmediateAbort
    ImmediateSuspend
    OnEntryOnExitDoFrontDoBack
     
    ☞ Capabilities, zero or more depending on what needs be enabled, are provided as state or region class template parameters.
    ☞ Arrows are always source-attached to the state that carries them. — So that the target never has to be provided with the same capability unless it serves as the source of other arrows of the same type.
     
  • The following table shows which capabilities are supported by type.
     
    State typesRegion type
    TopInitFinalBasicSuperCompositeRegion
    CapabilitiesSIGNAL ✓ ✓ ✓ ✓
    REACT ✓ ✓ ✓ ✓
    CONTINUE ✓ ✓ ✓ ✓
    PREEMPT ✓ ✓
    IMMEDIATE_PREEMPT ✓ ✓
    ON_ENTRY ✓ ✓ ✓ ✓
    ON_EXIT ✓ ✓ ✓ ✓
    DO_FRONT ✓ ✓ ✓ ✓
    DO_BACK ✓ ✓ ✓ ✓
     
    ☞ If a state or region is provided with a capability it does not support, then this provision is ignored, that is to say, it does not trigger any error condition.

State hierarchy

  • The following table shows what state hierarchy relationships are possible in ZFSM. Some relationships are mandatory, which means that a context must have an element of the given type. — Top states, superstates, and regions must have exactly one designated initial state, and composite states must have at least one region. Otherwise, a context may have one or more elements of the given type. Whenever a 0 appears, the context element in question cannot appear in the given context.
     
    Context
    TopSuperCompositeRegion
    Context elementSignal ≥ 0 ≥ 0 ≥ 0 ≥ 0
    Init 1 10 1
    Final ≥ 0 ≥ 00 ≥ 0
    Basic ≥ 0 ≥ 00 ≥ 0
    Super ≥ 0 ≥ 00 ≥ 0
    Composite ≥ 0 ≥ 00 ≥ 0
    Region00 ≥ 10
     
    ☞ A composite state with just one region is equivalent to a superstate whose contents are identical to that region's. — The reason why this pattern is not flagged as an error is that it may sometimes be useful in debugging state machines.
    ☞ Having more than one final state in one and the same context usually does not make much sense. Nevertheless, this pattern is not forbidden since it is deemed to not do any great harm either.

Signals

  • A signal is a condition that can be set during an execution tick. — It will automatically reset at the end of this tick. The following table shows the signal constructor plus what member functions exist on signals.
     
    Signal<T>(Context *context)Constructing a value-carrying signal within an enclosing context.
    raise(T const &value)Raising the signal, together with copying the value to be carried
    raise(T &&value)Raising the signal, together with moving the value to be carried
    bool operator()Query operator returning true, if and only if, the signal has been raised
    T const& getValue()Obtaining a const reference to the signal value
    T&& takeValue()Obtaining an r-value reference to the signal value for it to become movable
     
    The following table shows the signal constructor plus what member functions exist on void, that is to say, non-value carrying signals.
     
    Signal<>(Context *context)Constructing a void signal within an enclosing context.
    raise()Raising the signal, together with copying the value to be carried
    bool operator()Query operator returning true, if and only if, the signal has been raised
     
    ☞ The context becomes the scope of the signal, that is to say, the signal can be used in all contexts that lie anywhere in the hierarchy underneath.
    ☞ Using the signal outside of its scope leads to undefined behavior but is currently not explicitly flagged as an error condition. It is up to users to ensure that signals are never used out of scope.

Arrows

  • The following table shows the arrow types provided by ZFSM.
     
    ReactAn arrow that can be executed at the beginning of a tick
    ContinueAn arrow that can be executed in succession to executing one of the other arrow types
    Abort / SuspendAn arrow that can be executed to abort or suspend a superstate or composite state, but only after a recursive execution sequence within that state has finished
    ImmediateAbort / ImmediateSuspendAn arrow that can be executed to abort or suspend a superstate or composite state, and without starting to execute the current tick within that state
     
  • The following table shows the main arrow constructors, where Arrow can be one of the types shown in the previous table.
     
    Arrow<T>(Source &source, std::function<Carry<T>()> guard, Target &target, std::function<void(T)> payload)
    Arrow<>(Source &source, std::function<bool()> guard, Target &target, std::function<void()> payload)
     
    ☞ If an arrow type is parameterized with a non-void type of T, then the arrow guard must yield a carry if the guard's outcome is that the arrow fires. In that case the guard ought to return this result by means of a return statement of the form return {true, expression}, where the expression part forms the carry value. The carry value is automatically forwarded to the payload, which must be a callable taking a T as its argument. If the arrow does not fire, then the guard ought to return false and nothing else. In that case no constructor on T will be called to construct the carry, whence T need not be default constructible.
    ☞ If the template argument to the arrow type is empty or, equivalently, if is void, then the guard must always yield a bool while the payload must be parameter-less.
    ☞ If a lambda is used as a guard, and this lambda returns a carry, then it may be required to declare the carry type as the lambda's return type. Analogously, bool-returning guards may have to have bool declared as their return type.
    ☞ Both Source and Target must be state types. Cross-border arrows are not supported, that is to say, source and target must always lie within the same context. If that is not the case, then the way in which the state machine behaves is undefined.
     
  • The following table shows arrow constructors providing various shortcuts for the main constructors from the previous table.
     
    Arrow<>(Source &source, Target &target)An arrow without any guard and without any payload
    Arrow<>(Source &source, std::function<bool()> guard, Target &target)An arrow with a guard but without any payload
    Arrow<>(Source &source, Target &target, std::function<void()> payload)An arrow without a guard but with a payload
    Arrow<>(Source &source, Signal<T> &signal, Target &target)An arrow with a signal as its guard, and without any payload
    Arrow<T>(Source &source, Signal<T> &signal, Target &target, std::function<void(T)> payload)An arrow with a value-carrying signal as its guard, and with a matching payload
    Arrow<>(Source &source, Signal<T> &signal, Target &target, std::function<void()> payload)An arrow with a value-carrying signal as its guard, and with a payload that does not require any value argument
     
    ☞ If a signal appears as a guard, then that guard will fire if, and only if, that signal is raised at the time of the guard being evaluated.
     
  • The following table shows which source/target combinations are supported by which arrow types.
     
    Target
    InitFinalBasicSuperComposite
    SourceInit-
    React
    Continue
    React
    Continue
    React
    Continue
    React
    Continue
    Final-----
    Basic-
    React
    Continue
    React
    Continue
    React
    Continue
    React
    Continue
    Super-allallallall
    Composite-allallallall
     
    ☞ To summarize, an initial state can never be the target of any arrow, while a final state can never be the source of any arrow. A basic state can only be the source of React and Continue arrows. Only superstates and composite states can be the source or the target of any type of arrow.
     

Context actions

  • The following table shows which types of context actions are provided by ZFSM.
     
    OnEntryAn action executed when entering the context it belongs to
    OnExitAn action executed when exiting the context it belongs to
    DoFrontAn action executed prior to executing a complete tick sequence within the context it belongs to
    DoBackAn action executed after executing a complete tick sequence within the context it belongs to
     
  • The following table shows the main action constructors, where Action can be one of the types shown in the previous table.
     
    Action<T>(Context *context, std::function<Carry<T>()> guard, std::function<void(T)> payload)
    Action<>(Context *context, std::function<bool()> guard, std::function<void()> payload)
     
    ☞ Carry values within actions work completely analogous to carry values within arrows.
     
  • The following table shows context action constructors providing various shortcuts for the main constructors from the previous table. — See above for analogous arrow constructors.
     
    Action<>(Context *context, std::function<void()> payload)
    Action<T>(Context *context, Signal<T> &signal, std::function<void(T)> payload)
    Action<>(Context *context, Signal<T> &signal, std::function<void()> payload)
     

Top state public member functions

  • The following table shows the only public member functions that exist on top states.
     
    void fuse()Fusing the state machine right after constructing it
    bool tick(...)Triggering an execution tick performed by the state machine according to the recursive displine described below
    void exit()Performing a complete reset on the state machine
     
    ☞ fuse() has to be invoked right after constructing a state machine, typically in its constructor itself. As a result of invoking fuse(), the state machine is initialized for the first time.
    ☞ Arguments to tick(...) are zero or more expressions of the form signal.raise(...). This syntax makes it possible to prime an execution tick as part of the expression triggering it.
    ☞ tick(...) returns true if, and only if, the top state is not in its final state upon completing the execution tick.
    ☞ exit() has to be invoked on a state machine if it is to be re-initialized, even after the state machine has terminated. The state machine having terminated is, however, not a precondition for being able to invoke exit(). — It can be exited at any time.
    ☞ Entry actions are performed on a top state when invoking tick(...) for the first time after the state machine has been initialized.
    ☞ Exit actions are performed on a top state on invoking exit().

Execution discipline

    • The execution discipline is straightforward and recursive. The following points describe it in detail.
    • Basic terminology:
      • A hierarchical state is a superstate or composite state.
      • A preemptive arrow is an Abort or Suspend arrow.
      • An immediately preemptive arrow is an ImmediateAbort or ImmediateSuspend arrow.
      • An arrow guard opens if evaluating it yields true in the case of a bool-returning guard, or a carry with true in its first position in the case of a carry-returning guard, or if the guard consists of a signal and the signal is raised at the point of time of evaluating it as a guard.
      • A top state, superstate, or concurrent region has terminated if its current state is a final state. A composite state has terminated if all of its concurrent regions have terminated.
      • A Continue arrow is enabled if the following conditions hold true.
        • Its source state must be the current state within its context.
        • If its source state is a hierarchical state, then that state must have terminated.
        • Its guard must open.
      • Suspending a state means suspending it in full depth, something which is often called a deep suspend. ZFSM has no direct support for suspending states only at their topmost level while resetting every level underneath it, which is what is often just called a suspend.
    • The initial state of a state machine is the initial state of its top state.
    • An execution tick is begun by calling member function bool tick(...) on the state machine's top state. — For details, see the previous section on public member functions provided by top states.
    • Executing a top state, superstate, or concurrent region means the following.
      • If the current state is an initial state, a basic state, or a hierarchical state that has terminated, then the React arrows sourced at this state are considered in the order in which they have been constructed. Among these arrows, the first arrow whose guard opens is executed.
      • If the current state is a superstate or composite state that has not yet terminated, then the following execution discipline applies:
        • The immediately preemptive arrows sourced at this state are considered in the order in which they have been constructed. Among these arrows, the first arrow whose guard opens is executed.
        • If no immediately preemptive arrow is executed, then the hierarchical state itself is executed. Executing a hierarchical state means recursive descent in the case of a superstate and recursive descent into each region in the case of a composite state. Regions are executed in the order in which they have been constructed.
        • Once the hierarchical state is finished executing, and given it has not yet terminated, the preemptive arrows sourced at this state are considered in the order in which they have been constructed. Among these arrows, the first arrow whose guard opens is executed.
      • Once the first React arrow has been executed, Continue arrows are executed until no one is enabled anymore.
    • Within any context, only the arrows sourced at that context's current state are eligible for execution. Executing an arrow means the following.
      • If the arrow is a preemptive or immediately preemptive arrow, then the following applies:
        • If the arrow is an Abort or ImmediateAbort arrow, then a deep reset is applied to its source state. — So that the source returns to its initial state.
        • If the arrow is a Suspend or ImmediateSuspend arrow, then the source state is suspended. — So that the source continues in its state if and when it is re-entered.
      • Unless the arrow is a Suspend or ImmediateSuspend arrow, the source state is exited and returns to its initial state.
      • The arrow's payload is executed. It the payload expects a carry, then the carry is supplied from the arrow guard.
      • The arrow's target state becomes the new current state.
      • If the target state is a hierarchical state, then it is entered given it has not been suspended previously. In case it has been suspended, it continues from its suspended state. In either case, the following holds.
        • If the target state is a superstate, then Continue arrows are executed within it until no one is enabled anymore.
        • If the target state is a composite state, then Continue arrows are executed within its regions until no one is enabled anymore. These execution sequences take place in the order in which these regions have been constructed.
    • Context actions are executed as follows but always in the order in which they have been constructed.
      • OnEntry and OnExit actions on a context are executed whenever the context is entered or exited, respectively. As an exception, OnEntry actions are not executed whenever a suspended state is re-entered.
      • DoFront and DoBack actions on a context are executed before or after every execution sequence within that context, respectively. — They are not executed when entering the context.
    • If a scope is exited during an execution tick, then signals raised in this scope are reset immediately, that is to say, not waiting for the end of the overall execution tick.

    ☞ Handling signals in the way just described is related to what is known as the reincarnation problem, which is something that cannot be dwelled upon at this place. Suffice it to say that the reincarnation problem occurs in connection with a fundamental concepts called perfect synchrony, execution intervals or macrosteps vs. microsteps in executing synchronous programs. These concepts have their origin in emulating the workings of electric circuitry in software. While ZFSM is inspired by synchronous programming in general and SyncCharts in particular, it foregoes implementing these concepts in any bona fidae way. The only parallel that could be made is execution ticks being a kind of lightweight version of synchronous macrosteps. The tradeoff is that ZFSM practically does not have to introduce any extra overhead to deal with the relation between signals being raised or not and the flow of causality during macrosteps or execution ticks, to the extent that the macrostep/execution tick analogy holds. These matters are what normally cause restrictions on how to use signals and/or computational overhead in implementing synchronous macrosteps in software. One of the net effects of ZFSM's non-electric approach is the reincarnation problem not being an issue.
    ☞ In a similar vein, ZFSM does not require arrow guards to be free of any observable side effects. ZFSM guarantees that guards are evaluated in order whenever arrows are considered for execution according to the recursive execution discipline, and not any more often than that. Here, the outcome is, while it could be considered desirable to keep arrow guards free of any observable side effects, it does not lead to any undefined behaviour if users introduce them as they see fit.

Error handling

By default, most errors are flagged by throwing instances of class Exception, which itself derives from std::exception. Defining symbol ZFSM_ERRORS_ASSERT replaces exceptions by assert's becoming false; Defining symbol ZFSM_ERRORS_ASSUME_NONE makes the library code assume that no errors occur. These two symbols must not be defined at the same time. Then, two types of errors are currently not flagged at all:

  1. One and the same instance of class Init used as the designated initial state in more than one context.
  2. Signals used out of scope.

Examples

Signals as guards

The following example summarizes how signals can directly occur as guards. These patterns can occur in all types of arrows and context actions.

  #include <cassert>
  #include <iostream>
  #include "zfsm.hpp"

  using namespace zfsm;

  class SignalsAsArrowGuards : public Top<SIGNAL, ON_ENTRY>
  {
  public:
    Signal<> a{this};
    Signal<int> b{this};

  private:
    Init<REACT> init{this};
    Basic<CONTINUE> s{this};
    Basic<CONTINUE> t{this};
    Final final{this};

    React<> init_2_s{init, a, s, []{ std::cout << "Non-value carrying signal a used as a guard." << std::endl; }};
    Continue<> s_2_t{s, b, t, []{ std::cout << "Value-carrying signal b used as a guard while not retrieving its value." << std::endl; }};
    Continue<int> t_2_final{t, b, final, [](int v){ std::cout << "Value-carrying signal b used as a guard, retrieving value of " << v << '.' << std::endl;  }};

  public:
    SignalsAsArrowGuards() : Top<SIGNAL, ON_ENTRY>(init) { fuse(); }
  };

  int main()
  {
    SignalsAsArrowGuards example;
    assert (!example.tick(example.a.raise(), signalsAsArrowGuards.b.raise(42)));
    return 0;
  }

Context actions

The following example shows all possibilities of placing context actions.

  #include <cassert>
  #include <iostream>
  #include "zfsm.hpp"

  using namespace zfsm;

  class ContextActions : public Top<ON_ENTRY, DO_FRONT, DO_BACK, ON_EXIT>
  {
    OnEntry<> onEntry{this, []{ std::cout << "On entry at top" << std::endl; }};
    DoFront<> doFront{this, []{ std::cout << "Do front at top" << std::endl; }};
    DoBack<> doBack{this, []{ std::cout << "Do back at top" << std::endl; }};
    OnExit<> onExit{this, []{ std::cout << "On exit at top" << std::endl; }};

    Init<REACT> init{this};

    class S : public Super<CONTINUE, ON_ENTRY, DO_FRONT, DO_BACK, ON_EXIT> 
    {
      OnEntry<> onEntry{this, []{ std::cout << "On entry at s" << std::endl; }};
      DoFront<> doFront{this, []{ std::cout << "Do front at s" << std::endl; }};
      DoBack<> doBack{this, []{ std::cout << "Do back at s" << std::endl; }};
      OnExit<> onExit{this, []{ std::cout << "On exit at s" << std::endl; }};

      Init<CONTINUE> init{this};

      class C : public Composite<CONTINUE, ON_ENTRY, DO_FRONT, DO_BACK, ON_EXIT>
      {
        OnEntry<> onEntry{this, []{ std::cout << "On entry at c" << std::endl; }};
        DoFront<> doFront{this, []{ std::cout << "Do front at c" << std::endl; }};
        DoBack<> doBack{this, []{ std::cout << "Do back at c" << std::endl; }};
        OnExit<> onExit{this, []{ std::cout << "On exit at c" << std::endl; }};

        class R : public Region<ON_ENTRY, DO_FRONT, DO_BACK, ON_EXIT>
        {
          OnEntry<> onEntry{this, []{ std::cout << "On entry at r" << std::endl; }};
          DoFront<> doFront{this, []{ std::cout << "Do front at r" << std::endl; }};
          DoBack<> doBack{this, []{ std::cout << "Do back at r" << std::endl; }};
          OnExit<> onExit{this, []{ std::cout << "On exit at r" << std::endl; }};

          Init<CONTINUE> init{this};
          Basic<REACT> mid1{this};
          Basic<REACT> mid2{this};
          Final final{this};

          Continue<> init_2_mid1{init, mid1, []{ std::cout << "init_2_mid1" << std::endl; }};
          React<> mid1_2_mid2{mid1, mid2, []{ std::cout << "mid1_2_mid2" << std::endl; }};
          React<> mid2_2_final{mid2, final, []{ std::cout << "mid2_2_final" << std::endl; }};

        public:
          R(C *ctxt) : Region<ON_ENTRY, DO_FRONT, DO_BACK, ON_EXIT>(ctxt, init) {}
        }
        r{this};

      public:
        C(S *ctxt) :  Composite<CONTINUE, ON_ENTRY, DO_FRONT, DO_BACK, ON_EXIT>(ctxt) {}      
      }
      c{this};

      Final final{this};

      Continue<> init_2_c{init, c, []{ std::cout << "init_2_c" << std::endl; }};
      Continue<> c_2_final{c, final, []{ std::cout << "c_2_final" << std::endl; }};

    public:
      S(ContextActions *ctxt) :  Super<CONTINUE, ON_ENTRY, DO_FRONT, DO_BACK, ON_EXIT>(ctxt, init) {} 
    }
    s{this};

    Final final{this};

    React<> init_2_c{init, s, []{ std::cout << "init_2_s" << std::endl; }};
    Continue<> c_2_final{s, final, []{ std::cout << "s_2_final" << std::endl; }};

  public:
    ContextActions() : Top<ON_ENTRY, DO_FRONT, DO_BACK, ON_EXIT>(init) { fuse(); }
  };

  int main()
  {
    ContextActions example;
    assert (example.tick());
    assert (example.tick());
    assert (!example.tick());
    example.exit();
    return 0;
  }

Non-default constructible signal and carry values

The following example shows that values in signals and carries need not be default-constructible.

  #include <cassert>
  #include <iostream>
  #include <ctime>
  #include <cstdlib>
  #include "zfsm.hpp"

  using namespace zfsm;

  struct WrapInt
  {
      int const wrapped;

      WrapInt() = delete; 
      WrapInt(int k) : wrapped(k) {}
  };

  class NoDefaultConstructibleValues : public Top<SIGNAL>
  {
  public:
    Signal<WrapInt> sig{this};

  private:
    Init<REACT> init{this};
    Final final{this};

    React<WrapInt> init_2_final{
      init, 
      [&]()->Carry<WrapInt>{ 
        if (sig()) { 
          return {true, sig.getValue()}; 
        } else { 
          return false; 
        }}, 
      final, 
      [](WrapInt wrapper){ std::cout << "Retrieving random value of " << wrapper.wrapped << std::endl; }};

  public:
    NoDefaultConstructibleValues() : Top<SIGNAL>(init) { fuse(); }
  };

  main()
  {
    NoDefaultConstructibleValues example;
    std::srand(std::time({}));
    assert (!example.tick(example.sig.raise(std::rand())));
    return 0;
  }

State reuse

The following example shows a general pattern of reusing a state. Class Reusable provides a superstate type, which is instantiated with different external capabilities. The example contains two instances of S, one with an external capability of REACT, the other one with an external capability of CONTINUE.

  #include <cassert>
  #include <iostream>
  #include "zfsm.hpp"

  using namespace zfsm;

  template <Capability... capability>
  class Reusable : public Super<ON_ENTRY, ON_EXIT, capability...>
  {
  public:
    template<typename Ctxt>
    Reusable(Ctxt ctxt) : Super<ON_ENTRY, ON_EXIT, capability...>(ctxt, init) {}

  private:
    OnEntry<> onEntry{this, []{ std::cout << "At entry of S" << std::endl; }};
    OnExit<> onExit{this, []{ std::cout << "At exit of S" << std::endl; }};

    Init<CONTINUE> init{this};
    Final final{this};

    Continue<> init_2_final{init, final};
  };

  class StateReuse1 : public Top<>
  {
    Init<REACT> init{this};
    Reusable<REACT> s{this};
    Final final{this};

    React<> init_2_s{init, s};
    React<> s_2_final{s, final};

  public:
    StateReuse1() : Top<>(init) { fuse(); }
  };

  class StateReuse2 : public Top<>
  {
    Init<REACT> init{this};
    Reusable<CONTINUE> s{this};
    Final final{this};

    React<> init_2_s{init, s};
    Continue<> s_2_final{s, final};

  public:
    StateReuse2() : Top<>(init) { fuse(); }
  };

  main()
  {
    StateReuse1 example1;
    assert (example1.tick());
    assert (!example1.tick());

    StateReuse2 example2;
    assert (!example2.tick());

    return 0;
  }

State reuse with inheritance

The following, most basic example shows another general pattern of reusing a state, in this state by inheriting from it. What it means is that states cannot only be reused in a black-box fashion but can also be enriched, as it were, by using inheritance to add components to them.

  #include <cassert>
  #include <iostream>
  #include "zfsm.hpp"

  using namespace zfsm;

  template <Capability... capability>
  class Extensible : public Super<capability...>
  {
  public:
    template<typename Ctxt, typename Init>
    Extensible(Ctxt ctxt, Init &init) : Super<capability...>(ctxt, init) {}

    Final final{this};
  };

  class StateExtension : public Top<>
  {
    Init<REACT> init{this};

    class S : public Extensible<CONTINUE>
    {
      Init<CONTINUE> init{this};

      Continue<> init_2_final{init, final};

    public:
      S(StateExtension *ctxt) : Extensible<CONTINUE>(ctxt, init) {}
    }
    s{this};

    Final final{this};

    React<> init_2_s{init, s};
    Continue<> s_2_final{s, final};

  public:
    StateExtension() : Top<>(init) { fuse(); }
  };

  int main
  {
    StateExtension example;
    ASSERT(!example.tick());
    return 0;
  }

What's new

  • September 30, 2026: Version 0.1.4 has a bugfix and is accompanied by improved documentation.
  • September 26, 2026: Version 0.1.3 has a bugfix and is accompanied by improved documentation.
  • September 21, 2026: Version 0.1.2 has a minor bugfix and is accompanied by slightly improved documentation.
  • September 17, 2026: Version 0.1.1 has a bugfix and is accompanied by improved documentation.
  • August 24, 2026: Version 0.1 released.

Versioning policy

The version number will be advanced whenever there is any update to the library souce code. Mere documentation updates will not be reflected in the version number advancing.

Contact

Please send e-mail to get into touch. To help avoiding the use of 'cookies' on this Website while still providing spam protection, you are kindly asked to solve a puzzle to obtain the e-mail address. The puzzle is simply this. Consider the mock e-mail address, 'zfsm(dot)amlryar889(at)mbaldamus(dot)com'. To obtain the real e-mail address, substitute each character in between the '.' to the left of the '@' and the '@' itself — here represented by '(dot)' or '(a)', respectivey. Each character is to be substituted by the second-next character from the English alphabet with a wrap-around of 'y' being replaced by 'a', and 'z' being replaced by 'b'; Each digit is to be substituted by this digit plus two modulo 10.

The variable part will change in the event that the actual address gets flooded with spam. So, if a user finds an older address not working, then the current address can always be retrieved by solving the current puzzle as it appears at that point of time on this Web page.

Legal disclaimers

By using ZFSM or sending e-mail, you agree to the legal disclaimers shown below.

Legal disclaimer regarding the ZFSM library

All use of ZFSM library is governed by the Apache 2.0 license, which can be obtained at https://www.apache.org/licenses/LICENSE-2.0.

Legal disclaimer regarding this Website

Privacy

Neither any "cookies" nor any third-party analytics are used on this Website*. Likewise, it is neither required nor even possible to open any personal account on this Website. This Website does not collect any personal data by any other method either. Like with all other Websites, it is unavoidable that accessing it leads to anonymous, server-side traffic statistics being generated, a fact not mentioned in many privacy policies. These statistics are never used to try and identify any individual user by working back from the data available.

The only partial exception to all of that is users being able to send e-mail messages to the address that is presented under the heading "Contact." E-mails received are kept on the servers of the Internet provider (henceforth just "the Provider") employed to host this Website for the sole purpose of being able to satisfy any inquiries made — given that reacting is deemed warranted in the first place. E-mail messages are never stored elsewhere than on the servers deployed by the Provider. Users have no reason to doubt that the Provider deploys industry-standard procedures, equipment, and architectural provisions to keep their data secure. The only transfer that ever happens takes place transiently to view messages online on client systems run by the Owner of this Website (henceforth just "the Owner") but without ever storing any messages on these systems. Usually, any message received is read within three weeks after receiving it. Replying to a message may usually take three more weeks. In case a message is not reacted to, then it is deleted immediately after coming to this conclusion; In case a message is reacted to, then it is kept only for as long as is necessary to satisfy the inquiry made.

The Owner never shares any user data with any third parties unless required by applicable law.

Relevant authorities will be notified immediately whenever the Owner suspects that any leakage of user data might have occurred.

(* Just in case anyone wonders, cookie functionality normally induced by Doxygen has been disabled manually.)

Warranty disclaimer

While the Owner endeavours to keep this Website up to date and correct, the information offered herein is provided strictly on an “as is” and “as available” basis. Partaking in this information is at Users’ own risk. To the maximum extent permitted by applicable law, the Owner expressly disclaims all conditions, representations, and warranties — whether express, implied, statutory or otherwise, including, but not limited to, any implied warranty of merchantability, fitness for a particular purpose, or non-infringement of third-party rights. No advice or information, whether oral or written, obtained by User from Owner or through this Website will create any warranty not expressly stated herein. Any reliance you place on such information is therefore strictly at Users' own risk. In no event will the Owner be liable for any loss or damage including without limitation, indirect or consequential loss or damage, or any loss or damage whatsoever arising from loss of data or profits arising out of, or in connection with, partaking in the information provided herein.

Without limiting the foregoing, the Owner, its subsidiaries, affiliates, licensors, officers, directors, agents, co-branders, partners, suppliers, and employees do not warrant that this Website's contents are accurate, reliable, or correct; that they will meet Users’ requirements; that this Website will be available at any particular time or location, uninterrupted or secure; that any defects or errors will be corrected; or that this Website is free of viruses or other harmful components. Any content downloaded or otherwise obtained through the use of this Website is downloaded at Users' own risk and users shall be solely responsible for any damage to Users’ computer system or mobile device or loss of data that results from such download or Users’ use of this Website.

Every effort is made to keep this Website up and running smoothly. However, the Owner takes no responsibility for, and will not be liable for, this Website being unavailable at any time and any length of time for any reason.

The Owner has no obligation whatsoever to provide any technical support to users. Any technical support on the Owner's part is provided under the terms of the Warranty Disclaimer and entirely voluntarily. It can be terminated at any time without any prior notification and for any reason.

Imprint

This Website is owned by Michael Baldamus, Lindgårdsvägen 28, 743 64 Björklinge, Sweden.

Copyright

Copyright © 2026 by the Website Owner, all rights reserved.