Tiny C++ State Machine Implementation in C++

Overview

Overview

This is a tiny C++ API for writing state machines. It consists of a single header with a single class StateMachine. Users must define their StateMachine according to an interface that's not currently well-documented in the header but which should be clear from the following examples.

States in the state machine are referred to as "behaviors" and events are referred to as "messages".

I made this for fun so I did everything in the quickest way I could, so there may be issues or missing features. Feel free to file issues or make PRs if you'd like.

Examples

Compiling versions of these can be found here.

State machine with a single state that accepts a single message type

struct Msg1 {};

class SingleStateSingleMessageMachine { 
public:
    // Behaviors
    struct Running;
    
    // These using declarations are required. Message and Behavior can both either
    // be single types or std::variants of types.
    using Message = Msg1;
    using Behavior = Running;
    using InitialBehavior = Running;
    
    void prereceive() {
        globalCounter++;
    }
    
    void postreceive() {
    }
    
    struct Running {
        // This is a rough edge - It should be possible to not require the IgnoredMessages typedef 
        // if no messages should be ignored.
        using IgnoredMessage = void;

        std::optional<Behavior> receive(SingleStateSingleMessageMachine& self, const Msg1& m) {
            std::cout << "Running: received msg 1, globalCounter: " << self.globalCounter << std::endl;
            return std::nullopt;
        }
    };
    
    int globalCounter{0};
};

int main() {
    StateMachine<SingleStateSingleMessageMachine> boringStateMachine;
    boringStateMachine.receive(Msg1{});
    boringStateMachine.receive(Msg1{});
    /* Outputs:
    Running: received msg 1, globalCounter: 1
    Running: received msg 1, globalCounter: 2
    */
}

State machine with a multiple states and message types

struct Msg1 {};
struct Msg2 {};
struct Msg3 {};

class MyStateMachine { 
public:
    // Behaviors
    struct Start; struct Running; struct Stopped;
    
    using Message = std::variant<Msg1, Msg2, Msg3>;
    using Behavior = std::variant<Start, Running, Stopped>;
    using InitialBehavior = Start;
    
    void prereceive() {
        globalCounter++;
    }
    
    void postreceive() {
    }
        
    struct Start {
        // This specifies the messages that might be received but which should essentially
        // be ignored. In other words, it is not an error to receive these messages, but 
        // no behavior or transition should be executed upon receipt.
        using IgnoredMessage = std::variant<Msg2, Msg3>;
        // This specifies the valid next behaviors that can be transitioned to from
        // the start behavior. If any receive method tries to transition to a behavior
        // other than these, there will be a compilation error.
        using NextBehavior = std::variant<Running, Stopped>;
        
        std::optional<NextBehavior> receive(MyStateMachine& self, const Msg1& m) {
            std::cout << "Start: received msg 1, globalCounter: " << self.globalCounter << std::endl;
            // Returning a new behavior constitutes a state transition.
            return Running{};
        }
    };
    
    struct Running {
        using IgnoredMessage = Msg3;
        using NextBehavior = Stopped;

        std::optional<NextBehavior> receive(MyStateMachine& self, const Msg1& m) {
            std::cout << "Running: received msg 1, globalCounter: " << self.globalCounter << ", localCounter: " << localCounter << std::endl;
            ++localCounter;

            if (localCounter >= 2) {
                return Stopped{};
            } else {
                // Returning std::nullopt means that the current behavior is retained.
                return std::nullopt;
            }
        }

        std::optional<NextBehavior> receive(MyStateMachine& self, const Msg2& m) {
            std::cout << "Running: received msg 2, globalCounter: " << self.globalCounter << ", localCounter: " << localCounter << std::endl;
            ++localCounter;

            if (localCounter >= 2) {
                return Stopped{};
            } else {
                return std::nullopt;
            }
            
            return std::nullopt;
        }
        
        int localCounter{0};
    };
    
    struct Stopped {
        using IgnoredMessage = Msg3;
        using NextBehavior = Stopped;

        std::optional<NextBehavior> receive(MyStateMachine& self, const Msg1& m) {
            std::cout << "Stopped: received msg 1, globalCounter: " << self.globalCounter << std::endl;
            return std::nullopt;
        }

        std::optional<NextBehavior> receive(MyStateMachine& self, const Msg2& m) {
            std::cout << "Stopped: received msg 2, globalCounter: " << self.globalCounter << std::endl;
            return std::nullopt;
        }
    };
    
    int globalCounter{0};
};

int main() {
    StateMachine<MyStateMachine> myStateMachine;
    myStateMachine.receive(Msg1{});
    myStateMachine.receive(Msg1{});
    myStateMachine.receive(Msg2{});
    myStateMachine.receive(Msg1{});
    myStateMachine.receive(Msg2{});
    myStateMachine.receive(Msg3{});
    
    /* Outputs:
    Start: received msg 1, globalCounter: 1
    Running: received msg 1, globalCounter: 2, localCounter: 0
    Running: received msg 2, globalCounter: 3, localCounter: 1
    Stopped: received msg 1, globalCounter: 4
    Stopped: received msg 2, globalCounter: 5
    */
}
Owner
Matthew Saltz
Matthew Saltz
Automatic differentiation with weighted finite-state transducers.

GTN: Automatic Differentiation with WFSTs Quickstart | Installation | Documentation What is GTN? GTN is a framework for automatic differentiation with

null 56 Nov 30, 2021
Cobalt Strike Beacon Object File (BOF) that takes the name of of a PE file as an argument and spawns the process in a suspended state

Beacon Object File (BOF) that spawns an arbitrary process from beacons memory. Supports Parent Process ID (PPID) spoofing & blocking non-MS signed DLLs from loading into the processes memory (some EDR DLLs).

boku 293 Nov 29, 2021
EarlyBird process hollowing technique (BOF) - Spawns a process in a suspended state, inject shellcode, hijack main thread with APC, and execute shellcode

HOLLOW - Cobalt Strike BOF Authors: Bobby Cooke (@0xBoku) Justin Hamilton (@JTHam0) Octavio Paguaga (@OakTree__) Matt Kingstone (@n00bRage) Beacon Obj

Bobby Cooke 143 Nov 29, 2021
Universal State Monitor software for home automation input devices

Universal State Monitor Copyright 2019-2021 SuperHouse Automation Pty Ltd www.superhouse.tv A binary state monitor for DIY home automation projects. T

SuperHouse Automation 3 Aug 24, 2021
A kata to practice refactoring to the State Pattern

A kata to practice refactoring to the State Pattern

Barney Dellar 2 Sep 20, 2021
Hook up the OnePlus6(T) tri-state key in PostmarketOS!

OnePlus 6(T) tri-state key support in PostmarketOS As the name suggest, the goal of this little project is to hook up the OnePlus6(T) tri-state key in

Michele Perrone 7 Nov 14, 2021
A simple Z-Machine implementation in a single C file. Now with online multiplayer! :)

This is an implementation of Infocom's Z-Machine. The Z-Machine is a virtual machine that's something like a high-level CPU. To keep their games portable and easier to write, Infocom's games all use this fake processor and ship with a platform-specific Z-Machine "emulator" ... so a game could run wherever someone had implemented the Z-Machine.

Ryan C. Gordon 67 Nov 29, 2021
JoyfulNoise Tiny Utility Board

JoyfulNoise Tiny Utility Board A versatile ATtiny85-powered Eurorack module in 4HP. License JoyfulNoise hardware and software is Open Source. The JNTU

Ben Reeves 20 Nov 8, 2021
Elk is a tiny embeddable JavaScript engine that implements a small but usable subset of ES6

Elk is a tiny embeddable JavaScript engine that implements a small but usable subset of ES6. It is designed for microcontroller development. Instead of writing firmware code in C/C++, Elk allows to develop in JavaScript. Another use case is providing customers with a secure, protected scripting environment for product customisation.

Cesanta Software 1.2k Dec 7, 2021
Tiny FEL tools for allwinner SOC, support RISC-V D1 chip

XFEL Tiny FEL tools for allwinner SOC, support RISC-V D1 chip. How to build The xfel tools depends on the libusb-1.0 library, you need to install libu

xboot.org 34 Dec 3, 2021
A tiny external monitor for PC using STM32 and ST7789. Connects to PC over USB and displays the captured screen on ST7789 (240x240) display.

STM32 Tiny Monitor A super tiny monitor for your PC, suitable for your pet ant. A python script sends the captured screen over USB to the STM32 microc

Avra Mitra 51 Nov 21, 2021
Tiny weather station based on TTGO T5 V2.3.1 (ESP32 with 2.13

Tiny weather station based on TTGO T5 V2.3.1 (ESP32 with 2.13" e-ink display) Features wireless and rechargable weather description including temperat

Piotr Kubica 12 Nov 28, 2021
A tiny Forth I built in a week.

( Wrote a blog post about this here ) It was raining hard, a week ago. And what could you possibly do on a rainy Saturday afternoon? Well... You can m

Thanassis Tsiodras 49 Nov 16, 2021
Retro Tiny Multitasking system for Z80 based computers

RTM-Z80 RTM/Z80 is a multitasking kernel, built for Z80 based computers, written in Z80 assembly language, providing its users with an Application Pro

ladislau szilagyi 26 Nov 17, 2021
Tiny and cheap robot car for inspecting sewer pipes >= 125 mm. With pan servo for the ESP32-Cam module

ESP32-Cam Sewer inspection car Version 1.0.0 - work in progress Based on esp32-cam-webserver by Owen Carter. Additional Features Pan servo for the ESP

Armin 1 Nov 9, 2021
Tiny and portable usb host and device stack for mcu with usb ip

Tiny and portable usb host and device stack for mcu with usb ip

sakumisu 37 Nov 29, 2021
Love 6's Regular Expression Engine. Support Concat/Select/Closure Basic function. Hope u can enjoy this tiny engine :)

Regex_Engine Love 6's Blog Website: https://love6.blog.csdn.net/ Love 6's Regular Expression Engine Hope u can love my tiny regex engine :) maybe a fe

Love6 1 Nov 22, 2021
A tiny evolution simulator, which uses SDL2 and is written in C

evosim Evosim is a small evolution simulator. Evosim uses the SDL2 library for graphics. The program can be compiled by simply running make in the evo

Victor Ocampo 2 Nov 20, 2021
A tiny metaprogramming library

Meta: A tiny metaprogramming library Meta is a tiny and header-only C++11 metaprogramming library released under the Boost Software License. Supported

Eric Niebler 246 Dec 5, 2021