Search code examples
c++boostc++14state-machineboost-sml

[Boost::ext].SML: Is there a way to store a callback to later process an event?


I came from Boost MSM and I'm now trying to implement the same state machines with SML 1.1.3. With SML I don't have access to the state machine itself and I'm forced to work with injected dependencies. I'm also heavily using D-Bus and now I have problems making asynchronous D-Bus calls. The async D-Bus call is made in a method of the injected dependency. When the D-Bus calls is finished a callback inside the dependency is called. There I need a way to emit an event to the state machine. Storing sml::back::process didn't work and I could't find out if it would still be valid when the async D-Bus call finishes.

Example:

    auto queryDBusAction = []( Dep& dep, sml::back::process<Ev_Result> processEvent ) {
        dep.makeAsyncDBusCall( SomeCallback );
    };

...

class Dep
{
public:
    void makeAsyncDBusCall( SomeCallback cb )
    {
        _cb = cb;
        _client.someAsyncDBusCall( boost::bind( &Dep::dbusCallFinished, this, _1 ) );
    }

protected:
    DBusClient _client;
    SomeCallback _cb;

    void dbusCallFinished( Result& result, const DBus::Error& dbusError )
    {
        // Here I need a way/callback to emit an event
        // that gets processed by the state machine
        _cb( Ev_Result{result} );
    }
};


Solution

  • In order to get access to state machine itself, you need to separate your application class to base class and sub class. The base class has pure virtual member function declaration. You can call the member functions from sml transition table action.

    You need define the sub class after transition table definition. Now, the sub class can access whole definition of the transition table. That means you can contain sml::sm as a member of the sub class.

    So you can call sml backend's process_event() function from the member function. See memfun1().

    #include <iostream>
    #include <cassert>
    
    #include <boost/sml.hpp>
    #include <boost/asio.hpp>
    
    namespace sml = boost::sml;
    
    struct e1 {};
    struct e2 {};
    
    // Separate member function declarations as the base class
    struct app_base {
        virtual void memfun1() = 0;
        virtual void memfun2(int) const = 0;
        virtual ~app_base() = default;
    };
        
    struct app_table {
        auto operator()() const noexcept {
            using namespace sml;
            // I can write member function call in action thanks to base class app_base
            return make_transition_table(
                // source event      guard                     action                                    target
                *"s1"_s + event<e1>  [([] { return true; })] / [](app_base& appb)  { appb.memfun1(); }   = "s2"_s
                ,"s2"_s + event<e2>                          / [](app_base& appb)  { appb.memfun2(42); } = "s1"_s
            );
        }
    };
    
    struct app : app_base {
        app(boost::asio::io_context& ioc):ioc { ioc } {}
        
        // post asynchronous callback function that calls process_event()
        void memfun1() override {
            std::cout << __PRETTY_FUNCTION__ << std::endl;
            boost::asio::post(
                ioc, 
                [this] { 
                    std::cout << "async callback is called. call process_event()" << std::endl;
                    sm_.process_event(e2{});
    
                    using namespace sml;
                    assert(sm_.is("s1"_s));
                }
            );
        }
        void memfun2(int v) const override {
            std::cout << __PRETTY_FUNCTION__ << ":" << v << std::endl;
        }
    
        // state machine backend can be a member of application class
        sml::sm<app_table> sm_ { static_cast<app_base&>(*this) };
        boost::asio::io_context& ioc;
    };
    
    int main() {
        using namespace sml;
        boost::asio::io_context ioc; // for async operation example
        
        app a { ioc };
        assert(a.sm_.is("s1"_s));
        a.sm_.process_event(e1{});
        assert(a.sm_.is("s2"_s));
        
        ioc.run(); // async loop start until async event will become empty
    }
    

    Runnable Demo: https://wandbox.org/permlink/9XFQAMHuRcMPeU64