我第一次在 boost sml 中构建一个更大的状态机,并且需要一种方法来可视化(例如导出到 graphviz)整个状态机。知道如何才能做到这一点吗?有没有办法迭代状态机的结构并打印它?
免责声明:我对 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 渲染结果:
看起来有一种更简洁的方法来完成这些事情,尤其是在阅读
最新示例时存在嵌套 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,但实际上看起来更“值得信赖”,因为