C++ State Machine Library

Generate zero-overhead state machines from W3C SCXML. Compile-time enums, no virtual functions, 8-byte footprint. From embedded microcontrollers to Qt and GLib applications.

The Problem with C++ State Machines

Implementing state machines in C++ typically involves a choice between simplicity and correctness. Manual approaches using switch-case statements start simple but become unmaintainable as states and transitions grow. Framework-based solutions like Boost.Statechart or Qt State Machine add runtime overhead and force your code into specific class hierarchies.

Common approaches and their trade-offs

Approach Pros Cons
switch-case / enum Simple, no dependencies No hierarchy, hard to maintain, no formal verification
Boost.Statechart Full UML statechart support Heavy template usage, slow compilation, virtual dispatch
Boost.MSM Compile-time transitions Complex MPL syntax, long compile times, steep learning curve
Qt State Machine Integrates with Qt event loop Requires Qt dependency, QObject overhead, runtime construction
SCE (AOT) Zero overhead, W3C standard, compile-time enums Requires build-time code generation step

How SCE Works

SCE takes a different approach: you define your state machine in W3C SCXML (an XML-based standard for statecharts), and SCE's code generator produces a self-contained C++ header file at build time. The generated code uses compile-time enums for states and events, with no virtual functions, no RTTI, and no dynamic allocation.

Step 1: Define your state machine in SCXML

<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"
       name="TrafficLight" initial="red">
  <state id="red">
    <transition event="timer" target="green"/>
  </state>
  <state id="green">
    <transition event="timer" target="yellow"/>
  </state>
  <state id="yellow">
    <transition event="timer" target="red"/>
  </state>
</scxml>

Step 2: Generate C++ code

# Command line
sce-codegen generate traffic_light.scxml -o ./generated/ -l cpp

# Or with CMake (automatic)
sce_add_state_machine(TARGET my_app SCXML_FILE traffic_light.scxml)

Step 3: Use the generated state machine

#include "traffic_light_sm.h"
#include "wrappers/AutoProcessStateMachine.h"

int main() {
    using namespace SCE::Generated::traffic_light;

    SCE::Wrappers::AutoProcessStateMachine<traffic_light> light;
    light.initialize();                    // Enters "red" state
    light.processEvent(Event::Timer);      // red -> green
    light.processEvent(Event::Timer);      // green -> yellow
    light.processEvent(Event::Timer);      // yellow -> red
}

The generated header contains enum classes for State and Event, a compact transition table, and inline action handlers. No base class required, no framework lock-in. The state machine object starts at 8 bytes of memory.

C++ Function Integration

SCE allows you to call your C++ functions directly from SCXML transitions and actions using a Named Context pattern. Your business logic code remains completely independent of the state machine framework.

SCXML with Named Context

<!-- Declare Named Context: bind "hardware" to your C++ type -->
<scxml xmlns="http://www.w3.org/2005/07/scxml"
       xmlns:sce="urn:sce:extensions" xmlns:cpp="urn:sce:cpp"
       version="1.0" name="SmartLight" initial="off">
  <sce:context id="hardware" cpp:type="Hardware" cpp:include="hardware.h"/>

  <state id="off">
    <onentry>
      <script><cpp>hardware.powerOff()</cpp></script>
    </onentry>
    <transition event="switch_on" cond="cpp:hardware.hasPower()" target="on">
      <script><cpp>hardware.powerOn()</cpp></script>
    </transition>
  </state>

  <state id="on">
    <onentry>
      <script><cpp>hardware.setBrightness(100)</cpp></script>
    </onentry>
    <transition event="switch_off" target="off"/>
  </state>
</scxml>

Your C++ code (no framework dependency)

// hardware.h -- your business logic, completely independent
struct Hardware {
    bool hasPower() { return true; }
    void powerOn()  { /* ... */ }
    void powerOff() { /* ... */ }
    void setBrightness(int level) { /* ... */ }
};

Usage: Named Context is passed directly (no wrapper struct)

#include "smart_light_sm.h"

int main() {
    using namespace SCE::Generated::smart_light;

    // Named Context: pass your type directly as template parameter
    Hardware hardware;
    smart_light light(hardware);  // Dependency injection via constructor
    light.initialize();

    light.raiseExternal(Event::Switch_on);
    light.step();  // off -> on (calls hardware.powerOn())

    light.raiseExternal(Event::Switch_off);
    light.step();  // on -> off (calls hardware.powerOff())
}

The <sce:context> declaration generates a templated state machine class. Your type is the template parameter -- no wrapper struct needed. The generated code accesses your object via this->user_->hardware.powerOff(), providing zero-overhead direct calls with no virtual dispatch.

Platform Integration

SCE provides event dispatchers for the most common C++ application frameworks, enabling asynchronous state machine event processing:

Dispatcher Platform Timer Source
StdThreadDispatcher Any C++11 std::condition_variable
QtDispatcher Qt5 / Qt6 QTimer
GLibDispatcher GTK / GNOME GSource
#include "dispatchers/StdThreadDispatcher.h"
#include "wrappers/AsyncStateMachine.h"

auto dispatcher = StdThreadDispatcher::create();
AsyncStateMachine<traffic_light, Event> sm(dispatcher);

sm.initialize();
dispatcher->start();

// Post events from any thread (thread-safe)
sm.postEvent(Event::Timer);

dispatcher->stop();

Build System Integration

SCE integrates with CMake via three methods:

# FetchContent example (simplest)
include(FetchContent)
FetchContent_Declare(sce
    GIT_REPOSITORY https://github.com/newmassrael/scxml-core-engine.git
    GIT_TAG main)
FetchContent_MakeAvailable(sce)

add_executable(my_app main.cpp)
sce_add_state_machine(TARGET my_app SCXML_FILE my_machine.scxml)
target_link_libraries(my_app PRIVATE sce_runtime)

4-Tier Library Architecture

SCE's C++ library is split into four tiers so you can link only what you need:

Tier Dependencies Use Case
sce_core Header-only, zero Pure static AOT (embedded, microcontrollers)
sce_base + logger AOT with logging
sce_scripting + Lua/QuickJS Static hybrid (ECMAScript expressions)
sce_runtime + XML parser Full interpreter (runtime SCXML loading)

For most embedded use cases, sce_core (header-only, zero dependencies) is sufficient. The code generator automatically selects the optimal tier based on the SCXML features used.

Performance

W3C SCXML Compliance

SCE passes all 202 mandatory W3C SCXML 1.0 conformance tests, covering compound states, parallel states, history states, invoke, delayed events, and ECMAScript datamodels. The code generator automatically detects which features your SCXML uses and selects the optimal engine: