c++c++17boost-sml

How to add/initialize members in an SML state or state machine?


I designed this state machine using boost::sml

struct LooperStateMachine {
    auto operator()() const {
        using namespace sml;
        
        return make_transition_table(
                *"beggining"_s + event<heel> / onRecordFirstLoop.value() = "recording_first_loop"_s,
                "recording_first_loop"_s + event<heel>[is_heel_valid] / onRecordLoops.value() = "recording_other_loops_and_playing"_s,
                "recording_first_loop"_s + event<toe>[is_toe_valid] / onPlayLoops.value() = "playing_loops"_s,
                "playing_loops"_s + event<toe>[is_toe_valid]/onStopPlayingLoops.value() = "stopped"_s,
                "playing_loops"_s + event<heel>[is_heel_valid]/onRecordLoops.value() = "recording_other_loops_and_playing"_s,
                "recording_other_loops_and_playing"_s + event<toe>[is_toe_valid] / onPlayLoops.value() = "playing_loops"_s,
                "stopped"_s + event<toe>[is_toe_valid] / onClearLoops.value() = "beggining"_s,
                "stopped"_s + event<heel>[is_heel_valid] / onSaveLoops.value() = "stopped"_s
        );
        
    }

    std::optional<std::function<void()>> onClearLoops;
    std::optional<std::function<void()>> onSaveLoops;
    std::optional<std::function<void()>> onPlayLoops;
    std::optional<std::function<void()>> onRecordLoops;
    std::optional<std::function<void()>> onRecordFirstLoop;
    std::optional<std::function<void()>> onStopPlayingLoops;
};

However, to use it, the sm auto instantiates it:

int main() {
    using namespace sml;

    sm<LooperStateMachine> sm;
    

So I don't get a chance to set my functions.

How can I set the functions?


Solution

  • To your literal question you might just provide a default constructor/NSMI. However I get that you want to be able to "dynamically" switch out these action "hooks".

    About injecting the function hooks, the docs say:

    SML states cannot have data as data is injected directly into guards/actions instead

    This gave me the idea to separate your runtime state from the state machine:

    Aside: optional functions are redundant, function<> can already be value-less and has a convenient conversion to bool fdr that, just like optional<>.

    Live On Wandbox

    #include <boost/sml.hpp>
    #include <functional>
    #include <iostream>
    namespace sml = boost::sml;
    
    namespace looping {
        struct heel { };
        struct toe { };
        static auto is_heel_valid = []() { return false; };
        static auto is_toe_valid = []() { return false; };
    
        using DynamicAction = std::function<void()>;
        struct Hooks {
            DynamicAction onClearLoops;
            DynamicAction onSaveLoops;
            DynamicAction onPlayLoops;
            DynamicAction onRecordLoops;
            DynamicAction onRecordFirstLoop;
            DynamicAction onStopPlayingLoops;
        };
    
        static auto onClearLoops       = [](Hooks &hooks) { if (hooks.onClearLoops)       hooks.onClearLoops();       };
        static auto onSaveLoops        = [](Hooks &hooks) { if (hooks.onSaveLoops)        hooks.onSaveLoops();        };
        static auto onPlayLoops        = [](Hooks &hooks) { if (hooks.onPlayLoops)        hooks.onPlayLoops();        };
        static auto onRecordLoops      = [](Hooks &hooks) { if (hooks.onRecordLoops)      hooks.onRecordLoops();      };
        static auto onRecordFirstLoop  = [](Hooks &hooks) { if (hooks.onRecordFirstLoop)  hooks.onRecordFirstLoop();  };
        static auto onStopPlayingLoops = [](Hooks &hooks) { if (hooks.onStopPlayingLoops) hooks.onStopPlayingLoops(); };
    
        struct LooperStateMachine {
            auto operator()() const {
                using namespace sml;
                
                return make_transition_table(
                    *"beginning"_s + event<heel> / onRecordFirstLoop = "recording_first_loop"_s,
                    "recording_first_loop"_s + event<heel>[is_heel_valid] / onRecordLoops = "recording_other_loops_and_playing"_s,
                    "recording_first_loop"_s + event<toe>[is_toe_valid] / onPlayLoops = "playing_loops"_s,
                    "playing_loops"_s + event<toe>[is_toe_valid]/onStopPlayingLoops = "stopped"_s,
                    "playing_loops"_s + event<heel>[is_heel_valid]/onRecordLoops = "recording_other_loops_and_playing"_s,
                    "recording_other_loops_and_playing"_s + event<toe>[is_toe_valid] / onPlayLoops = "playing_loops"_s,
                    "stopped"_s + event<toe>[is_toe_valid] / onClearLoops = "beginning"_s,
                    "stopped"_s + event<heel>[is_heel_valid] / onSaveLoops = "stopped"_s
                );
            }
        };
    }
    
    int main() {
        looping::Hooks hooks;
        hooks.onClearLoops = [] { std::cout << "Clearing them\n"; };
    
        sml::sm<looping::LooperStateMachine> sm(hooks);
    }
    

    Disclaimer: I have only the flimsiest understanding of this library. I find it to be a mind bender to get to grips with, but in an increasingly positive way.