adr1611
adr1611

Reputation: 51

How to get current state from multiple nested transition tables using boost::sml library?

I have edited my post by adding an example. You can find the header, source, and main on this link. Minimized:

#include <boost/sml.hpp>
#include <iostream>
using namespace boost::sml;

struct Start {};
struct GoNested {};

struct Ts3 {
    auto operator()() {
        return make_transition_table(
                *"nested_s3"_s + event<Start> = "Ts3_1"_s,
                "nested_s3"_s + boost::sml::on_entry<_> / [] { std::puts("---- nested_s3 ----"); },
                "Ts3_1"_s + boost::sml::on_entry<_> / [] { std::puts("---- Ts3_1 ----"); }
                );
    }
};
struct Ts2 {
    auto operator()() {
        return make_transition_table(
                *"nested_s2"_s + event<Start> = "Ts2_1"_s,
                "Ts2_1"_s + event<GoNested> = state<Ts3>,
                "nested_s2"_s + boost::sml::on_entry<_> / [] { std::puts("---- nested_s2 ----"); },
                "Ts2_1"_s + boost::sml::on_entry<_> / [] { std::puts("---- Ts2_1 ----"); });
    }
};
struct Ts1 {
    auto operator()() {
        return make_transition_table(
                *"s1"_s + event<Start> = "s2"_s,
                "s2"_s + event<GoNested> = state<Ts2>,
                "s1"_s + boost::sml::on_entry<_> / [] { std::puts("---- s1 ----"); },
                "s2"_s + boost::sml::on_entry<_> / [] { std::puts("---- s2 ----"); }
                );
    }
};

int main() {
    boost::sml::sm<Ts1, Ts2, Ts3> sMachine_;

    auto print = [&] {
        auto vis = [](auto state) { std::cout << "Current state = " << state.c_str() << std::endl; };
        sMachine_.visit_current_states(vis);
    };

    print(); sMachine_.process_event(Start{});
    print(); sMachine_.process_event(GoNested{});
    print(); sMachine_.process_event(Start{});
    print(); sMachine_.process_event(GoNested{});
    print(); sMachine_.process_event(Start{});
    print();
}

Printing

---- s1 ----
Current state = s1
---- s2 ----
Current state = s2
---- nested_s2 ----
Current state = boost::ext::sml::v1_1_6::back::sm<boost::ext::sml::v1_1_6::back::sm_policy<Ts2> >
---- Ts2_1 ----
Current state = boost::ext::sml::v1_1_6::back::sm<boost::ext::sml::v1_1_6::back::sm_policy<Ts2> >
---- nested_s3 ----
Current state = boost::ext::sml::v1_1_6::back::sm<boost::ext::sml::v1_1_6::back::sm_policy<Ts2> >
---- Ts3_1 ----
Current state = boost::ext::sml::v1_1_6::back::sm<boost::ext::sml::v1_1_6::back::sm_policy<Ts2> >

I have 3 nested transitions tables (Ts#), each of them at a given level => Ts1(Ts2(Ts3)))

Meaning that the highest and first transition table called is Ts1 and the more the program move further, the more the state machine goes into Ts2 and then Ts3. Obviously, each transition table has its own state.

I am trying to get the current state from a given transition table but I don't succeed to get it if it is in ts2 or ts3.

This below piece of code

sMachine_.visit_current_states([](auto state) {
    std::cout << state.c_str() << std::endl; });

works only if I am at the first transition table (Ts1), otherwise, I got the following string instead of the state name:

boost::ext::sml::v1_1_4::back::sm<boost::ext::sml::v1_1_4::back::sm_policy<Ts2> >

Do you have any solution to get it correctly?

The above reproducible example depicts the problem by building and running the "testsSM.cpp" main file.

Many thanks in advance,

Upvotes: 4

Views: 710

Answers (1)

mahush
mahush

Reputation: 141

Minimal solution

In order to visit nested states, you need a more advanced visitor than the simple lambda function you have in place. In the examples of the boost sml library you can find a visitor implemented as follows:

template <typename StateMachine>
class StateVisitor
{
public:
  explicit StateVisitor(const StateMachine &state_machine) : state_machine_{state_machine} {}

  template <typename CompositeState>
  void operator()(boost::sml::aux::string<boost::sml::sm<CompositeState>>) const
  {
    std::cout << boost::sml::aux::get_type_name<CompositeState>() << ':';
    state_machine_.template visit_current_states<boost::sml::aux::identity<CompositeState>>(*this);
  }

  template <typename AnyOtherState>
  void operator()(AnyOtherState state) const
  {
    std::cout << AnyOtherState::c_str() << '\n';
  }

private:
  const StateMachine &state_machine_;
};

You can apply that visitor by changing your print lambda like this:

  auto print = [&]
  {
    StateVisitor<decltype(sMachine_)> visitor(sMachine_);
    sMachine_.visit_current_states(visitor);
  };

Be aware that the visitor is applied recursively. This is necessary because boost sml implements composite states as its own state machines. That means each state containing at least one nested state is itself a state machine. The function can handle composite states accordingly by providing an overloaded dedicated to composite states.

If you are happy with accessing the string representation of states you are done now.

Bonus solution

If you would like to access the specific type of a state you might extend the visitor as follows. This is especially useful if you define states as structs because this way you can access those structs statically.

template <typename StateMachine>
class BonusStateVisitor
{
public:
  explicit BonusStateVisitor(const StateMachine &state_machine) : state_machine_{state_machine} {}

  // Overload to handle states that have nested states
  template <typename CompositeState>
  void operator()(boost::sml::aux::string<boost::sml::sm<CompositeState>>) const
  {
    std::cout << boost::sml::aux::get_type_name<CompositeState>() << ':';
    state_machine_.template visit_current_states<boost::sml::aux::identity<CompositeState>>(*this);
  }

  // Overload to handle states without nested states and defines as: "state"_s
  template <char... Chars>
  void operator()(boost::sml::aux::string<boost::sml::aux::string<char, Chars...>> string_state) const
  {
    std::cout << string_state.c_str() << '\n';
  }

  // Overload to handle terminate states
  void operator()(boost::sml::aux::string<boost::sml::back::terminate_state> terminate_state) const
  {
    std::cout << terminate_state.c_str() << '\n';
  }

  // Overload to handle internal states
  void operator()(boost::sml::aux::string<boost::ext::sml::v1_1_8::front::internal>) const
  {
    // ignore internal states
  }

  // Overload to handle any other states, meaning states that are defined as: struct state{};)
  template <typename TSimpleState>
  void operator()(boost::sml::aux::string<TSimpleState>) const
  {
    std::cout << boost::sml::aux::get_type_name<TSimpleState>() << '\n';
  }

private:
  const StateMachine &state_machine_;
};

Upvotes: 2

Related Questions