sml icon indicating copy to clipboard operation
sml copied to clipboard

`c_str()` overload for sub state machines

Open mechatheo opened this issue 5 years ago • 9 comments

Currently it is possible to add a custom c_str() function to own state classes. However this does not work for sub state machines. Even with a custom c_str() function get_type_name is called and the result tends to be rather cryptic, because of all the nested internal namespaces.

It would be nice if one could add a c_str() function to sub state machines as it is possible with user defined state types.

mechatheo avatar Mar 20 '20 20:03 mechatheo

i found out that the internal boost::sml::back::policies::get_state_name_t can be used which does the right thing even for nested state machines.

mechatheo avatar Mar 21 '20 13:03 mechatheo

I'd like to visit current states recursively and produce a hierarchy. Could you provide more detail on how to achieve it?

lpc921 avatar Mar 25 '20 22:03 lpc921

Hi @lpc921. To get the full recursive name i've implemented the following helper functions. I'm not very proud of it and it has to be improved, but i needed something working quickly


template <typename SM>
std::string GetCurrentStateName(const SM& stateMachine)
{
    std::string name;
    stateMachine.visit_current_states([&name](const auto& state) {
        name = boost::sml::back::policies::get_state_name_t<std::decay_t<decltype(state)>>::c_str();
    });
    return name;
}

template <typename>
struct is_sub_state_machine : std::false_type
{};

template <class T, class... Ts>
struct is_sub_state_machine<boost::sml::back::sm<boost::sml::back::sm_policy<T, Ts...>>>
    : std::true_type
{};

template <typename>
struct state_machine_impl : std::false_type
{};

template <class T, class... Ts>
struct state_machine_impl<boost::sml::back::sm<boost::sml::back::sm_policy<T, Ts...>>>
{
    using type = T;
};

template <typename SSM, typename SM>
std::string GetCurrentSubStateName(const SM& stateMachine)
{
    std::string name;
    stateMachine.template visit_current_states<SSM>([&name, &stateMachine](const auto& state) {
        name = boost::sml::back::policies::get_state_name_t<std::decay_t<decltype(state)>>::c_str();
        using state_repr_t = std::decay_t<decltype(state)>;
        using state_t = typename state_repr_t::type;
        if constexpr (is_sub_state_machine<state_t>::value)
        {
            using state_machine_t = typename state_machine_impl<state_t>::type;
            name += ".";
            name +=
                GetCurrentSubStateName<decltype(boost::sml::state<state_machine_t>)>(stateMachine);
        }
    });
    return name;
}

template <typename SM>
std::string GetCurrentFullStateName(const SM& stateMachine)
{
    /** @todo find out the size at compile time and reserve accordingly */
    std::string name;
    stateMachine.visit_current_states([&name, &stateMachine](const auto& state) {
        name += GetCurrentStateName(stateMachine);
        using state_repr_t = std::decay_t<decltype(state)>;
        using state_t = typename state_repr_t::type;
        if constexpr (is_sub_state_machine<state_t>::value)
        {
            using state_machine_t = typename state_machine_impl<state_t>::type;
            name += ".";
            name +=
                GetCurrentSubStateName<decltype(boost::sml::state<state_machine_t>)>(stateMachine);
        }
    });
    return name;
}

To get nice names out of it, all entities must have a c_str() function, so not only states but also your sub state machines. For states you get this automatically if you use the "state"_s notation.

This solution requires c++17 because of the constexpr if however it surely can be rewritten without it.

mechatheo avatar Mar 28 '20 22:03 mechatheo

Hi @mechatheo, thanks a lot for providing the code and the hints. I am not familiar with template programming. Your advice helped a lot.

lpc921 avatar Mar 29 '20 04:03 lpc921

Currently it is possible to add a custom c_str() function to own state classes. However this does not work for sub state machines. Even with a custom c_str() function get_type_name is called and the result tends to be rather cryptic, because of all the nested internal namespaces.

It would be nice if one could add a c_str() function to sub state machines as it is possible with user defined state types.

Hi @mechatheo , How exactly do you mean add a custom c_str() function to own state class ? As far as I could see, the c_str() call within visit_current_states allways results in get_type_name implementation of sml.hpp.

This was my unsuccessful attempt to provide a custom c_str() function,.

class StateIdle {
public:
    const char* c_str() {
        return "Idle";
    }
};

// to be used in the transition table like:
state<StateIdle> + event<e> = state<class AnotherState>

CrazyDaisyRecords avatar Feb 03 '22 08:02 CrazyDaisyRecords

Hi @CrazyDaisyRecords :wave:

sorry it took me so long :see_no_evil:. I believe you're missing the static keyword in your example, so try this instead and let me know if it works for you.

class StateIdle {
public:
    static auto c_str() {
        return "Idle";
    }
};

mechatheo avatar Feb 08 '22 22:02 mechatheo

Hi @mechatheo, Thank you very much. That works for me! Awesome!

CrazyDaisyRecords avatar Feb 09 '22 06:02 CrazyDaisyRecords

@mechatheo I tried to make your code run on C++14 but without any success... It does work though in C++17 as you said! Regardless, I find the source code very hard to read so I came up with a different yet simpler way of getting the current full state name. You can interract with the source code here!

And here is the GitHub Repository I made for experimentation with Boost::SML https://github.com/xorz57/BoostSMLProject

Source Code

#include <boost/sml.hpp>

#include <cstdio>
#include <list>
#include <string>

#define ENTER1(State) (state<State> + sml::on_entry<_> / []() { Logger::Enter(#State); Logger::Print(); })
#define ENTER2() (X + sml::on_entry<_> / []() { Logger::Enter("X"); Logger::Print(); })

#define LEAVE1(State) (state<State> + sml::on_exit<_> / []() { Logger::Leave(); })
#define LEAVE2() (X + sml::on_exit<_> / []() { Logger::Leave(); })

class Logger {
public:
    static void Print() {
        bool first = true;
        for (const std::string &state : sStates) {
            if (!first) std::printf("/");
            std::printf("%s", state.c_str());
            first = false;
        }
        std::printf("\n");
    }

    static void Enter(const std::string &state) { sStates.emplace_back(state); }
    static void Leave() { sStates.pop_back(); }

private:
    static std::list<std::string> sStates;
};

std::list<std::string> Logger::sStates;

struct EventA1 { bool flag = false; };
struct EventA2 { bool flag = false; };
struct EventA3 { bool flag = false; };
struct EventB1 { bool flag = false; };
struct EventB2 { bool flag = false; };
struct EventB3 { bool flag = false; };

namespace sml = boost::sml;

struct StateA {
    struct StateA0 {};
    struct StateA1 {};
    struct StateA2 {};

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

        const auto GuardA1 = [](auto event) { return event.flag; };
        const auto GuardA2 = [](auto event) { return event.flag; };
        const auto GuardA3 = [](auto event) { return event.flag; };

        const auto ActionA1 = [](auto) { std::printf("ActionA1\n"); };
        const auto ActionA2 = [](auto) { std::printf("ActionA2\n"); };
        const auto ActionA3 = [](auto) { std::printf("ActionA3\n"); };

        return make_transition_table(
            // clang-format off
            *state<StateA0> + event<EventA1> [GuardA1] / ActionA1 = state<StateA1>
            ,state<StateA1> + event<EventA2> [GuardA2] / ActionA2 = state<StateA2>
            ,state<StateA2> + event<EventA3> [GuardA3] / ActionA3 = X
            ,ENTER1(StateA0)
            ,ENTER1(StateA1)
            ,ENTER1(StateA2)
            ,ENTER2()
            ,LEAVE1(StateA0)
            ,LEAVE1(StateA1)
            ,LEAVE1(StateA2)
            ,LEAVE2()
            // clang-format on
        );
    }
};

struct StateB {
    struct StateB0 {};
    struct StateB1 {};
    struct StateB2 {};

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

        const auto GuardB1 = [](auto event) { return event.flag; };
        const auto GuardB2 = [](auto event) { return event.flag; };
        const auto GuardB3 = [](auto event) { return event.flag; };

        const auto ActionB1 = [](auto) { std::printf("ActionB1\n"); };
        const auto ActionB2 = [](auto) { std::printf("ActionB2\n"); };
        const auto ActionB3 = [](auto) { std::printf("ActionB3\n"); };

        return make_transition_table(
            // clang-format off
            *state<StateB0> + event<EventB1> [GuardB1] / ActionB1 = state<StateB1>
            ,state<StateB1> + event<EventB2> [GuardB2] / ActionB2 = state<StateB2>
            ,state<StateB2> + event<EventB3> [GuardB3] / ActionB3 = X
            ,ENTER1(StateB0)
            ,ENTER1(StateB1)
            ,ENTER1(StateB2)
            ,ENTER2()
            ,LEAVE1(StateB0)
            ,LEAVE1(StateB1)
            ,LEAVE1(StateB2)
            ,LEAVE2()
            // clang-format on
        );
    }
};

struct Main {
    auto operator()() const noexcept {
        using namespace sml;
        Logger::Enter("Main");
        return make_transition_table(
            // clang-format off
            *state<StateA> = state<StateB>
            ,state<StateB> = X
            ,ENTER1(StateA)
            ,ENTER1(StateB)
            ,ENTER2()
            ,LEAVE1(StateA)
            ,LEAVE1(StateB)
            ,LEAVE2()
            // clang-format on
        );
    }
};

int main() {
    sml::sm<Main> sm;
    sm.process_event(EventA1{true});
    sm.process_event(EventA2{true});
    sm.process_event(EventA3{true});
    sm.process_event(EventB1{true});
    sm.process_event(EventB2{true});
    sm.process_event(EventB3{true});
}

Output

Main/StateA
Main/StateA/StateA0
ActionA1
Main/StateA/StateA1
ActionA2
Main/StateA/StateA2
ActionA3
Main/StateA/X
Main/StateB
Main/StateB/StateB0
ActionB1
Main/StateB/StateB1
ActionB2
Main/StateB/StateB2
ActionB3
Main/StateB/X
Main/X

xorz57 avatar Jul 07 '23 13:07 xorz57

Nevermind! Just found this undocumented example. By undocumented I mean it wasn't documented on the wiki https://boost-ext.github.io/sml/examples.html. I had to look up the repository.

xorz57 avatar Jul 10 '23 10:07 xorz57