DrDreadful
DrDreadful

Reputation: 65

How to pass data to an event inside a boost::sml state machine for using the 'direct' process method?

Consider the example from the sml documentation regarding process and defer, rewritten a bit by me to pass a dependency to the state machine to be used in the event processing:


// $CXX -std=c++14 defer_and_process.cpp
#include <boost/sml.hpp>
#include <cassert>
#include <deque>
#include <queue>

namespace sml = boost::sml;

namespace {
struct e1 {};
struct e2 {int dependency = 0;};
struct e3 {};
struct e4 {};

struct defer_and_process {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
       *"idle"_s + event<e1> / defer
      , "idle"_s + event<e2> = "s1"_s
      , "s1"_s   + event<e1> / process(e2{/*pass my_dependency here*/}) = "s2"_s // this is the interesting bit
      , "s2"_s   + event<e3> / process(e4{})
      , "s2"_s   + event<e4> = X
    );
  }
};
}  // namespace

int main() {
  int my_dependency = 42;
  using namespace sml;
  sm<defer_and_process, sml::defer_queue<std::deque>, sml::process_queue<std::queue>>
      sm{my_dependency}; 
  assert(sm.is("idle"_s));

  assert(sm.process_event(e1{}));
  assert(sm.is("idle"_s));

  assert(!sm.process_event(e2{}));  /// triggers idle -> s1 and s1 -> s2 (via deferred e1)
  assert(sm.is("s2"_s));

  assert(sm.process_event(e3{}));  /// triggers s2.process(e4) -> X (via processed e4)
  assert(sm.is(sml::X));
}

Is it possible to do that directly or...

Is the lambda way described in Boost State Machine Language - `post` event from within an `action`

The only way to do it? Is inlining not possible?

Like process(e2{some way to pull the data out of the sm dependency list and pass it here }) ?

Upvotes: 2

Views: 605

Answers (2)

DrDreadful
DrDreadful

Reputation: 65

Apparently the callable object is the way to go. Got it.

This means that the context of the state machine class/struct does not have access to any injected objects, in the case of the original post, my_dependency.

On the other hand, actions and guards benefit from the automatic dependency injection. The solution to the original problem is to create an action, lambda or an class object with () operator overload, and use that instead of the built-in process action.

The difference is that with the built-in process action, the event is initialized in the state machine context, where the dependencies are not available. With a custom action, the dependencies get injected.

Upvotes: 0

sehe
sehe

Reputation: 393664

Putting in a static dependency already just works. If it needs to be dynamic, then the callable object approach is required because there's no other way to access the context:

// $CXX -std=c++14 defer_and_process.cpp
#include <boost/sml.hpp>
#include <cassert>
#include <deque>
#include <functional>
#include <iostream>
#include <queue>

namespace sml = boost::sml;

namespace {
    struct e1 {};
    struct e2 { int dependency = 0; };
    struct e3 {};
    struct e4 {};

    struct injectable {
        int dependency;
    };

    struct defer_and_process {
        auto operator()() const noexcept {
            using namespace sml;

            auto action = [](injectable& i, sml::back::process<e2> process) {
                std::cout << "posting with e2{" << i.dependency << "}" << std::endl;
                process(e2{i.dependency});
            };

            return make_transition_table(             //
                *"idle"_s + event<e1> / defer,        //
                "idle"_s + event<e2>        = "s1"_s, //
                "s1"_s + event<e1> / action = "s2"_s, //
                "s2"_s + event<e3> / process(e4{}),   //
                "s2"_s + event<e4> = X                //
            );
        }
    };
} // namespace

int main() {
    for (auto dep : {0, 42, -314}) {
        using namespace sml;
        sm<                                //
            defer_and_process,             //
            sml::defer_queue<std::deque>,  //
            sml::process_queue<std::queue> //
        > sm{injectable{dep}};

        assert(sm.is("idle"_s));

        assert(sm.process_event(e1{}));
        assert(sm.is("idle"_s));

        assert(!sm.process_event(e2{})); /// triggers idle -> s1 and s1 -> s2 (via deferred e1)
        assert(sm.is("s2"_s));

        assert(sm.process_event(e3{})); /// triggers s2.process(e4) -> X (via processed e4)
        assert(sm.is(sml::X));
    }
}

Note that you can hide that callable with some syntactic sugar. For example:

#include <boost/core/demangle.hpp>
#include <boost/sml.hpp>
#include <cassert>
#include <deque>
#include <functional>
#include <iostream>
#include <queue>

namespace sml = boost::sml;

namespace {
    struct injectable {
        int dependency = 0;
    };

    struct e1 {};
    struct e2 {
        int dependency;
        e2(injectable const& i = {}) : dependency(i.dependency) {}
    };
    struct e3 {};
    struct e4 {
        int dependency;
        e4(injectable const& i = {}) : dependency(i.dependency) {}
    };

    template <typename E> struct inj_process {
        void operator()(injectable& i, sml::back::process<E> process) const {
            std::cout << "posting with " << boost::core::demangle(typeid(E).name()) << "{" << i.dependency
                      << "}" << std::endl;
            process(E{i});
        };
    };

    struct defer_and_process {
        auto operator()() const noexcept {
            using namespace sml;

            return make_transition_table(                        //
                *"idle"_s + event<e1> / defer,                   //
                "idle"_s + event<e2>                   = "s1"_s, //
                "s1"_s + event<e1> / inj_process<e2>{} = "s2"_s, //
                "s2"_s + event<e3> / inj_process<e4>{},          //
                "s2"_s + event<e4> = X                           //
            );
        }
    };
} // namespace

int main() {
    for (auto dep : {0, 42, -314}) {
        using namespace sml;
        sm<                                //
            defer_and_process,             //
            sml::defer_queue<std::deque>,  //
            sml::process_queue<std::queue> //
        > sm{injectable{dep}};

        assert(sm.is("idle"_s));

        assert(sm.process_event(e1{}));
        assert(sm.is("idle"_s));

        assert(!sm.process_event(e2{})); /// triggers idle -> s1 and s1 -> s2 (via deferred e1)
        assert(sm.is("s2"_s));

        assert(sm.process_event(e3{})); /// triggers s2.process(e4) -> X (via processed e4)
        assert(sm.is(sml::X));
    }
}

Printing

posting with (anonymous namespace)::e2{0}
posting with (anonymous namespace)::e4{0}
posting with (anonymous namespace)::e2{42}
posting with (anonymous namespace)::e4{42}
posting with (anonymous namespace)::e2{-314}
posting with (anonymous namespace)::e4{-314}

Upvotes: 0

Related Questions