【问题标题】:visualize boost sml state machine可视化 boost sml 状态机
【发布时间】:2020-06-23 07:46:18
【问题描述】:

我第一次在 boost sml 中构建了一个更大的状态机,并且需要一种方法来可视化(例如导出到 graphviz)整个状态机。知道怎么做吗?有什么方法可以遍历状态机的结构并打印出来吗?

【问题讨论】:

    标签: c++ boost


    【解决方案1】:

    免责声明:我对 SML 的经验为零(必须找到它所在的位置)。

    第一次尝试,嗯

    但是,尝试使用 visit_current_states 接口做一些事情时,我想出了这个......在给定事件列表的情况下映射出(复合)状态机的不太好的实现:

    #include <boost/sml.hpp>
    
    namespace sml = boost::sml;
    namespace aux = sml::aux;
    
    struct e1 {};
    struct e2 {};
    struct e3 {};
    struct e4 {};
    struct e5 {};
    
    struct sub {
        auto operator()() const {
            using namespace sml;
            // clang-format off
            return make_transition_table(
                 *"idle"_s + event<e3> = "sub1"_s
                , "sub1"_s + event<e4> = X
            );
            // clang-format on
        }
    };
    
    struct composite {
        auto operator()() const {
            using namespace sml;
            // clang-format off
            return make_transition_table(
                 *"idle"_s   + event<e1> = "s1"_s
                , "s1"_s     + event<e2> = state<sub>
                , state<sub> + event<e5> = X
            );
            // clang-format on
        }
    };
    
    #include <boost/hana.hpp>
    #include <boost/core/demangle.hpp>
    #include <iostream>
    #include <iomanip>
    
    namespace mapper {
        namespace hana = boost::hana;
        using namespace std::string_literals;
        using boost::core::demangle;
        
        template <typename F> struct ycombine {
            ycombine(F f):f(f) {}
            F f;
            template <typename... A>
            auto operator()(A... a) const { return f(*this, a...); };
        };
    
        hana::tuple<e1,e2,e3,e4,e5> events;
    
        template <class TSM> class Vis {
          public:
            explicit Vis(const TSM& sm, std::string prefix = "") : sm_{ sm }, prefix(prefix) {}
    
            template <class TSub>
            void operator()(aux::string<boost::sml::sm<TSub>>) const {
                auto subname = aux::get_type_name<TSub>();
    
                Vis nvis(sm_, prefix + '/' + subname);
                sm_.template visit_current_states<aux::identity<TSub>>(nvis);
                prefix = nvis.prefix;
            }
    
            template <class TState> void operator()(TState state) const {
                prefix += "/"s + state.c_str();
            }
    
          private:
            const TSM& sm_;
          public:
            mutable std::string prefix;
        };
    
        template <typename SM>
        std::string get_current(SM const& sm) {
            Vis<SM> v{sm};
            sm.visit_current_states(v);
            return v.prefix;
        };
    }
    
    int main() {
        using namespace mapper;
    
        auto recurse = ycombine {
            [](auto self, auto sm) {
                hana::for_each(events, [=](auto ev) {
                    auto clone = sm;
                    auto from = get_current(clone);
                    clone.process_event(ev);
                    auto to = get_current(clone);
    
                    if (from != to) {
                        std::cout
                            << std::quoted(from) << " -> "
                            << std::quoted(to) 
                            << " [label=" << std::quoted(demangle(typeid(ev).name())) << "]\n";
    
                        self(clone);
                    }
                });
            } };
    
        std::cout << "digraph {\n";
        recurse(sml::sm<composite>{});
        std::cout << "}\n";
    }
    

    虽然我不知道这有多大用处,但至少我可以用 graphviz 渲染结果:

    第二次尝试 - 阅读精细手册 :derp:

    看起来有一种更简洁的方法来做这些事情,尤其是在阅读 latest examples 时嵌套 typedef transitions 的存在:

    // $CXX -std=c++14 plant_uml.cpp
    #include <boost/sml.hpp>
    #include <cassert>
    #include <iostream>
    #include <string>
    #include <typeinfo>
    
    namespace sml = boost::sml;
    
    struct e1 {};
    struct e2 {};
    struct e3 {};
    struct e4 {};
    
    struct guard {
      bool operator()() const { return true; }
    } guard;
    
    struct action {
      void operator()() {}
    } action;
    
    struct plant_uml {
      auto operator()() const noexcept {
        using namespace sml;
        return make_transition_table(
           *"idle"_s + event<e1> = "s1"_s
          , "s1"_s + event<e2> [ guard ] / action = "s2"_s
          , "s2"_s + event<e3> [ guard ] = "s1"_s
          , "s2"_s + event<e4> / action = X
        );
      }
    };
    
    template <class T>
    void dump_transition() noexcept {
      auto src_state = std::string{sml::aux::string<typename T::src_state>{}.c_str()};
      auto dst_state = std::string{sml::aux::string<typename T::dst_state>{}.c_str()};
      if (dst_state == "X") {
        dst_state = "[*]";
      }
    
      if (T::initial) {
        std::cout << "[*] --> " << src_state << std::endl;
      }
    
      std::cout << src_state << " --> " << dst_state;
    
      const auto has_event = !sml::aux::is_same<typename T::event, sml::anonymous>::value;
      const auto has_guard = !sml::aux::is_same<typename T::guard, sml::front::always>::value;
      const auto has_action = !sml::aux::is_same<typename T::action, sml::front::none>::value;
    
      if (has_event || has_guard || has_action) {
        std::cout << " :";
      }
    
      if (has_event) {
        std::cout << " " << boost::sml::aux::get_type_name<typename T::event>();
      }
    
      if (has_guard) {
        std::cout << " [" << boost::sml::aux::get_type_name<typename T::guard::type>() << "]";
      }
    
      if (has_action) {
        std::cout << " / " << boost::sml::aux::get_type_name<typename T::action::type>();
      }
    
      std::cout << std::endl;
    }
    
    template <template <class...> class T, class... Ts>
    void dump_transitions(const T<Ts...>&) noexcept {
      int _[]{0, (dump_transition<Ts>(), 0)...};
      (void)_;
    }
    
    template <class SM>
    void dump(const SM&) noexcept {
      std::cout << "@startuml" << std::endl << std::endl;
      dump_transitions(typename SM::transitions{});
      std::cout << std::endl << "@enduml" << std::endl;
    }
    
    int main() {
      sml::sm<plant_uml> sm;
      dump(sm);
    }
    

    输出,以获得灵感:

    @startuml
    
    [*] --> idle
    idle --> s1 : e1
    s1 --> s2 : e2 [guard] / action
    s2 --> s1 : e3 [guard]
    s2 --> terminate : e4 / action
    
    @enduml
    

    显然,不是graphviz,但实际上看起来更“值得信赖”,因为

    • 它类似于某种标准的 UML 表示法,
    • 由图书馆作者撰写
    • 包括我无法提供的详细信息(主要是因为不知道它们的存在/工作)

    【讨论】:

    • 天哪,我怎么会忽略转储示例:(该死的...谢谢!
    • 好吧#metoo 我猜:)
    • 我印象深刻,你从没看过 SML 就知道了。您最初是如何发现 visit_current_states 的?我已经查看页面boost-ext.github.io/sml/index.html 3 周了,我今天才发现它,试图了解 visitor.cpp 示例中发生了什么
    • 我想我对库源进行了一些关键字搜索,包括示例。我记得我在最后一个样本中匹配了它?
    • 该示例似乎不是in the example list,尽管您可以仍然找到它here。似乎该示例只是 added May 26th 并且提交消息确认它没有被充分记录。我很确定我刚刚进行了物理结帐,而我恰好是在添加样本之后的一个月 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-15
    • 2019-02-02
    • 1970-01-01
    • 2017-01-06
    • 1970-01-01
    相关资源
    最近更新 更多