Skip to content

fsm::thread_safe_fsm (Thread-Safe MPSC Engine)

fsm::thread_safe_fsm<TransitionTable, Context, InitialState, Policy> is an asynchronous Multi-Producer Single-Consumer (MPSC) state machine execution engine designed for multi-threaded services, robotics middleware (ROS 2), game engine architectures, and high-concurrency systems.


Class Template Synopsis

namespace fsm {

template <
    typename Table,
    typename Context = no_context,
    typename InitialState = typename Table::initial_state
>
class thread_safe_fsm {
public:
    using table_type   = Table;
    using context_type = Context;
    using state_type   = InitialState;

    // Constructors & Lifecycle
    explicit thread_safe_fsm(Context ctx = Context{});
    ~thread_safe_fsm();

    // 1. Asynchronous Fire-and-Forget Enqueue
    template <typename Event>
    void post(Event&& event);

    // 2. Asynchronous Enqueue with std::future Result
    template <typename Event>
    std::future<dispatch_result> post_async(Event&& event);

    // 3. Synchronous Dispatch (Blocks caller thread until execution completes)
    template <typename Event>
    dispatch_result dispatch_sync(const Event& event);

    // Thread-Safe State Inspection
    [[nodiscard]] std::string_view current_state_name() const;
    [[nodiscard]] std::size_t state_index() const;

    template <typename State>
    [[nodiscard]] bool is_in_state() const;

    // Synchronized Context Access
    template <typename Func>
    void with_context(Func&& fn);

    template <typename Func>
    auto with_context(Func&& fn) const;

    // Worker Control
    void stop();
    [[nodiscard]] bool is_running() const noexcept;
    [[nodiscard]] std::size_t pending_events_count() const;
};

} // namespace fsm

Multi-Threaded Execution Flow

flowchart TD
    subgraph Producers["Concurrent Producer Threads"]
        T1["Thread 1 (e.g. Network Socket)"]
        T2["Thread 2 (e.g. User Interface)"]
        T3["Thread 3 (e.g. Sensor Poller)"]
    end

    subgraph MPSCQueue["Thread-Safe Event Queue"]
        Queue["Thread-Safe Bounded Ring Queue<br/>Protected by Mutex or Spinlock"]
    end

    subgraph ConsumerWorker["Background Dedicated Worker Thread"]
        Worker["Worker Loop (condition_variable wait)"]
        CoreFSM["fsm::fsm Core Instance Execution"]
        Promise["Fulfill std::promise"]
        Worker --> CoreFSM
        CoreFSM --> Promise
    end

    T1 -->|post| Queue
    T2 -->|post_async| Queue
    T3 -->|post| Queue
    Queue -->|dequeue and notify| Worker

Complete Multi-Threaded Example

#include "uav_fsm.hpp"
#include <iostream>
#include <thread>
#include <vector>

struct FlightContext {
    int battery{100};
    bool emergency{false};
};

// Define clean type alias for thread-safe asynchronous FSM
using ThreadSafeUavFSM = fsm::thread_safe_fsm<AutonomousUavMissionTable, FlightContext, Preflight>;

int main() {
    FlightContext ctx;
    ThreadSafeUavFSM fsm(ctx);

    std::cout << "Initial: " << fsm.current_state_name() << "\n";


    // 1. Thread 1: Post async calibration and wait for completion
    std::thread worker1([&fsm]() {
        std::future<fsm::dispatch_result> fut = fsm.post_async(CalibrationOk{});
        fsm::dispatch_result res = fut.get(); // Wait for worker thread execution
        std::cout << "[Thread 1] Calibration status: " << res.to_string() << "\n";
    });

    // 2. Thread 2: Fire-and-forget commands
    std::thread worker2([&fsm]() {
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
        fsm.post(TakeoffCmd{});
    });

    worker1.join();
    worker2.join();

    // Give worker time to process takeoff
    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    // Inspect safely from main thread
    std::cout << "Current State: " << fsm.current_state_name() << "\n";

    // Inspect context safely
    fsm.with_context([](const FlightContext& c) {
        std::cout << "Context Battery: " << c.battery << "%\n";
    });

    return 0;
}