c++boostfsmstatechart

FSM libraries do not offer UML "do activity" implementation


I've looked for state machine (FSM, HSM ...) C++ libraries on GitHub (Boost, tinyFSM ...) and I've noticed that none of them offering the UML "do activity" inside a state. They only offer a single action during a transition when triggered by an event (fsm.process_event(Event{})). By activity, I mean a "long time action" (like done inside a thread but cancelable when an event occurred see https://cs.emis.de/LNI/Proceedings/Proceedings07/TowardEfficCode_3.pdf for more reference). I do not necessary need a real std::thread, but a tick() method (aka on_running()) to be called from my states class in concurrency.

Pseudocode summarizing my idea:

struct State1 { void on_running() { ... blocking code but quick reactive } };
FSM fsm; // + init of transition table

void threaded_rountine()
{
  fsm.process_event(Event1{});
}

int main()
{
  fsm.process_event(Event2{});
  while (true)
  {
    fsm.tick(); // will call fsm.current_state.on_running() in which I can call eventually call fsm.process_event(Event3{});
  }
}

Why, I asked for that ? Because I want to integrate a library offering a .tick() method for updating.

Note: in UML, if a state is transitioning to itself it will not call on_entering and on_leaving actions (implemented by constructor/destructor in boost), therefore I'm not sure, i.e. in Boost if I add a transition with an action for the same source/target state, I can mimic a on_running method).

Boost statechart:

Do activity

- Not supported in Boost.Statechart
- A do activity can be simulated with a separate thread that is started in the entry action and cancelled (!) in the exit action of a particular state

Currently, I tried https://www.itemis.com/en/products/itemis-create/documentation/user-guide/overview_what_are_state_machines which allows activities, but this is a proprietary tool.

So my questions are:


Solution

  • It is not strange FSM's usually don't provide this out of the box. Doing states model responsiveness and thus their activities cannot run on the mainthread that manages the state of the statemodel (or must be interruptible on single threaded systems). This example shows how to do that using multithreading.

    Demo : https://onlinegdb.com/f0P4557kd

    #include <chrono>
    #include <future>
    #include <mutex>
    #include <condition_variable>
    #include <iostream>
    
    using namespace std::chrono_literals;
    
    class fsm_t
    {
    public:
        void on_enter_doing_state()
        {
            start_activity();
        }
    
        void on_exit_doing_state()
        {
            cancel_activity();
        }
    
    
    private:
        void start_activity()
        {
            m_activity_future = std::async(std::launch::async,[this]{ do_activity(); });
        }
    
        void cancel_activity()
        {
            {
                std::unique_lock lock{m_mtx};
                m_cancelled = true;
                m_cv.notify_all();
            }
    
            // wait for activity to finish (cancelled)
            m_activity_future.get();
        }
    
    
        void do_activity()
        {
            for(std::size_t n = 0; n < 50; ++n)
            {
                std::unique_lock lock{m_mtx};
    
                // simulate doing some work for 100ms and then check cancellation
                m_cv.wait_for(lock,100ms, [&]{ return m_cancelled; });
                
                //co-operative cancellation of activity!
                if(m_cancelled) 
                {
                    std::cout << "\ncancelled\n";
                    return;
                }
                std::cout << (n%10);
            }
    
            // send activity finished event to fsm.
            // send_event(event::done);
        }
    
        std::future<void> m_activity_future;
        bool m_cancelled{false};
        std::mutex m_mtx;
        std::condition_variable m_cv;
    };
    
    int main()
    {
        fsm_t fsm;
        fsm.on_enter_doing_state();
        std::this_thread::sleep_for(2s);
        fsm.on_exit_doing_state(); // will cancel, activity not finished yet (full activity will take 5s)
    }