可视化 boost sml 状态机

问题描述 投票:0回答:1

我第一次在 boost sml 中构建一个更大的状态机,并且需要一种方法来可视化(例如导出到 graphviz)整个状态机。知道如何才能做到这一点吗?有没有办法迭代状态机的结构并打印它?

c++ boost boost-sml
1个回答
2
投票

免责声明:我对 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:

看起来有一种更简洁的方法来完成这些事情,尤其是在阅读

最新示例
时存在嵌套 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 符号,
  • 由库作者编写
  • 包括我无法做到的细节(主要是因为不知道它们的存在/工作)

© www.soinside.com 2019 - 2024. All rights reserved.