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.