DEV Community

Cover image for Building Better State Machines in Modern C++: CXXStateTree
ZigRazor
ZigRazor

Posted on

Building Better State Machines in Modern C++: CXXStateTree

Building Better State Machines in Modern C++: CXXStateTree

State machines are the backbone of countless software systems - from game AI and UI flows to embedded devices and network protocols. Yet, implementing them in C++ often feels like we're stuck in the past: endless switch statements, tangled conditionals, and boilerplate that makes your eyes glaze over.

What if there was a better way?

๐Ÿš€ Meet CXXStateTree

CXXStateTree is a modern, header-only C++20 library that brings elegance and performance to state machine development. It's designed for developers who want clean, maintainable code without sacrificing the performance C++ is known for.

๐Ÿ’ก Why Another State Machine Library?

Fair question! Here's what makes CXXStateTree different:

โšก Zero Heap Allocation

Every allocation counts in performance-critical applications. CXXStateTree is designed with a zero-allocation philosophy - everything stays on the stack where possible.

๐Ÿ”ง Fluent Builder API

No more fighting with complex constructors or configuration objects. Build your state machine the way you'd draw it on a whiteboard:

#include <iostream>
#include "CXXStateTree/StateTree.hpp"

using namespace CXXStateTree;

int main() {
    auto machine = StateTree::Builder()
        .initial("Idle")
        .state("Idle", [](State& s) {
            s.on("Start", "Running", nullptr, []() {
                std::cout << "๐Ÿš€ Starting engine..." << std::endl;
            });
        })
        .state("Running", [](State& s) {
            s.on("Stop", "Idle", nullptr, []() {
                std::cout << "๐Ÿ›‘ Stopping engine..." << std::endl;
            });
        })
        .build();

    machine.send("Start");  // Output: ๐Ÿš€ Starting engine...
    machine.send("Stop");   // Output: ๐Ÿ›‘ Stopping engine...
}
Enter fullscreen mode Exit fullscreen mode

Clean. Readable. Self-documenting.

๐Ÿ›ก๏ธ Guards and Actions

Real-world state machines need conditional logic. CXXStateTree lets you attach guards (conditions) and actions to transitions:

auto doorMachine = StateTree::Builder()
    .initial("Closed")
    .state("Closed", [](State& s) {
        s.on("Open", "Open", 
            []() { 
                // Guard: only open if unlocked
                return !isDoorLocked(); 
            },
            []() { 
                // Action: perform the opening
                std::cout << "๐Ÿšช Opening door..." << std::endl;
            }
        );
    })
    .state("Open", [](State& s) {
        s.on("Close", "Closed", nullptr, []() {
            std::cout << "๐Ÿšช Closing door..." << std::endl;
        });
    })
    .build();
Enter fullscreen mode Exit fullscreen mode

๐ŸŒณ Hierarchical States (Available!)

One of the most powerful features - nested states for modeling complex behaviors:

Game Character
โ”œโ”€โ”€ Alive
โ”‚   โ”œโ”€โ”€ Idle
โ”‚   โ”œโ”€โ”€ Walking
โ”‚   โ””โ”€โ”€ Running
โ””โ”€โ”€ Dead
Enter fullscreen mode Exit fullscreen mode

This allows you to handle events at different levels of specificity, reducing code duplication and improving maintainability.

๐ŸŽฏ Real-World Use Cases

CXXStateTree shines in:

  • ๐ŸŽฎ Game Development: Character AI, game flow, animation controllers
  • ๐Ÿ“ฑ UI Development: Form wizards, navigation flows, modal states
  • ๐Ÿค– Embedded Systems: Protocol handlers, device state management
  • ๐ŸŒ Network Programming: Connection state machines, protocol implementations
  • ๐Ÿฆพ Robotics: Behavior trees, task scheduling

๐Ÿ“ฆ Getting Started

Requirements

  • C++20 compiler (GCC โ‰ฅ 10, Clang โ‰ฅ 11, MSVC โ‰ฅ 2019)
  • CMake (for building)

Installation

Option 1: Single Header (Recommended)

git clone https://github.com/ZigRazor/CXXStateTree.git
cd CXXStateTree
cmake -S . -B build -DENABLE_SINGLE_HEADER=ON
cmake --build build
Enter fullscreen mode Exit fullscreen mode

The single header file will be in single_include/CXXStateTree.hpp - just drop it into your project!

Option 2: Shared Library

cmake -S . -B build
cmake --build build
Enter fullscreen mode Exit fullscreen mode

Quick Example: Traffic Light

Let's build a simple traffic light system:

#include <iostream>
#include <thread>
#include <chrono>
#include "CXXStateTree/StateTree.hpp"

using namespace CXXStateTree;
using namespace std::chrono_literals;

int main() {
    auto trafficLight = StateTree::Builder()
        .initial("Red")
        .state("Red", [](State& s) {
            s.on("Timer", "Green", nullptr, []() {
                std::cout << "๐Ÿ”ด -> ๐ŸŸข RED to GREEN" << std::endl;
            });
        })
        .state("Green", [](State& s) {
            s.on("Timer", "Yellow", nullptr, []() {
                std::cout << "๐ŸŸข -> ๐ŸŸก GREEN to YELLOW" << std::endl;
            });
        })
        .state("Yellow", [](State& s) {
            s.on("Timer", "Red", nullptr, []() {
                std::cout << "๐ŸŸก -> ๐Ÿ”ด YELLOW to RED" << std::endl;
            });
        })
        .build();

    // Simulate traffic light cycle
    for (int i = 0; i < 6; i++) {
        std::this_thread::sleep_for(2s);
        trafficLight.send("Timer");
    }

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ—๏ธ Project Quality

CXXStateTree isn't just elegant code - it's built with professional software engineering practices:

  • โœ… Google Test Integration - Comprehensive unit test suite
  • โœ… Codecov Integration - Track test coverage
  • โœ… GitHub Actions CI/CD - Every commit is validated
  • โœ… Modern CMake - Easy integration into existing projects

๐Ÿ—บ๏ธ Roadmap

The project has an exciting future ahead:

Status Version Features
โœ… v0.1.0 Basic state machine with transitions
โœ… v0.2.0 Guards and actions
โœ… v0.3.0 Nested hierarchical states
โœ… v0.4.0 Graphviz export
๐Ÿšง v0.5.0 Coroutine/async support
๐Ÿ“‹ v1.0.0 Full test coverage, benchmarks, docs

๐Ÿค Contributing

CXXStateTree is open source under the MPL 2.0 license and welcomes contributions!

Ways to contribute:

  • ๐Ÿ› Report bugs and issues
  • ๐Ÿ’ก Suggest new features
  • ๐Ÿ”ง Submit pull requests
  • ๐Ÿ“š Improve documentation
  • โšก Share performance benchmarks

๐ŸŽ“ Learning Resources

Want to dive deeper? Check out:

๐Ÿ’ญ Final Thoughts

State machines don't have to be a pain in C++. With CXXStateTree, you get:

  1. Developer Productivity - Write less, express more
  2. Maintainability - Clear structure that scales
  3. Performance - Zero-overhead abstractions
  4. Modern C++ - Built for C++20 and beyond
  5. Active Development - Regular updates and improvements

Whether you're building a game, an embedded system, or a complex business application, CXXStateTree provides a solid foundation for managing state transitions cleanly and efficiently.

๐Ÿš€ Try It Today

git clone https://github.com/ZigRazor/CXXStateTree.git
Enter fullscreen mode Exit fullscreen mode

Give it a star โญ if you find it useful, and share your experience in the comments below!


What kind of state machines are you building? Drop a comment and let's discuss! ๐Ÿ’ฌ


Who Am I?

Top comments (6)

Collapse
ย 
embeddedk8 profile image
Kate โ€ข โ€ข Edited

Hi, I have taken a look and I have some questions:

  1. Is there a way of ignoring events that are not handled in current state, instead of throwing?
    What I mean: imagine a elevator state machine with external button. User calls GoUp, elevator state goes Idle->GoingUp, and in this state, user is still pushing GoUp button which should have no effect. I don't want it to throw, but ignore this event.

  2. Is there a way of performing actions on entry/exit of the state, instead of particular transition?
    Assume there are many connections to i.e. Idle state. StateA->Idle, StateB->Idle, StateC->Idle. Same with going Idle->StateA/B/C (three transitions). Instead of executing something on each of these transitions, I only want to write this code once, on Idle entry, and Idle exit.

  3. Heap allocations - you wrote:

CXXStateTree is designed with a zero-allocation philosophy - everything stays on the stack where possible.

"where possible" is concerning - so does it mean there are always 0 dynamic allocations, or sometimes it allocates?

Collapse
ย 
zigrazor profile image
ZigRazor โ€ข

Hi Kate,
I answer you:
1) in this moment is possible to ignore event without throwing managing the event with null action. You can open an issue to add the functionality to automatically ignore events not managed.
2) In this moment is not provided this kind of mechanism, but will be developed soon
3) There is no explicit new in the code, but obviously the use of standard library container implicitly use heap-allocation.

Thank you for you feedback, if you have other question you can ask me.
Feel free to open issue for new functionalities or bug that you find.

Collapse
ย 
embeddedk8 profile image
Kate โ€ข โ€ข Edited

Great,
1- I have opened the issue.
2- good! It will be useful.
3- well so it means the whole benefit of: Zero Heap Allocation is not true and should not be raised. If embedded system is restricted to avoid heap allocations, it cannot use this state machine implementation.

It would be cool to add like the compile time option DISABLE_HEAP that would ensure that heap is not used at all, but it would be huge change in code, as there are many stl containers used now... but it would be really beneficial for embedded systems

Thread Thread
ย 
zigrazor profile image
ZigRazor โ€ข

1) thank you so much;
2) You can open an issue for this new functionality
3) I agree with you, it could be misundestood. I think that you could open an issue with the request for this major change. And it will be developed ASAP.

Thank you in advance!

Collapse
ย 
embeddedk8 profile image
Kate โ€ข

Looks interesting - I will play with it in my free time and try to use it in embedded sw.

Collapse
ย 
zigrazor profile image
ZigRazor โ€ข

Thank you so much for your insterest, give me feedback on it.