我正在使用 Boost::Ext SML 库创建一个状态机。我有许多状态(A、B、C、D...),在大多数情况下,状态会根据常见事件转换到另一个主题。例如,如果处于状态 A 或 B 或 C 且处理事件“GoToD”,则状态将转换为 D。据我所知,无法使用上述库对这种共性进行编码,并且由于我的状态数增加,我的转换表变得非常大而且脆弱。它已经看起来像这样了:
struct MyStateMachine {
auto operator()() const noexcept {
return sml::make_transition_table(
*sml::state<A> + sml::event<GoToB> = sml::state<B>,
sml::state<A> + sml::event<GoToC> = sml::state<C>,
sml::state<A> + sml::event<GoToD> = sml::state<D>,
sml::state<B> + sml::event<GoToA> = sml::state<A>,
sml::state<B> + sml::event<GoToC> = sml::state<C>,
sml::state<B> + sml::event<GoToD> = sml::state<D>,
sml::state<C> + sml::event<GoToA> = sml::state<A>,
sml::state<C> + sml::event<GoToB> = sml::state<B>,
sml::state<C> + sml::event<GoToD> = sml::state<D>,
sml::state<D> + sml::event<GoToA> = sml::state<A>,
sml::state<D> + sml::event<GoToB> = sml::state<B>,
sml::state<D> + sml::event<GoToC> = sml::state<C>,
}
};
上面我省略了一些细微差别(例如,常见转换上有一些防护措施),但这触及了我遇到的问题的根源。我想知道是否有一种编程方式来通用这些转换,或者有一种更好的方式来思考这个状态机的基本设计。
我尝试编写辅助函数(见下文)来编码常见转换,但我不确定如何这样做,以便可以使用此库将它们合并到单个转换表中。我也考虑过使用预处理器宏,但我不喜欢这个解决方案。
template <typename From>
auto make_common_transitions() {
return sml::make_transition_table(sml::state<From> + sml::event<GoToA> = sml::state<A>,
sml::state<From> + sml::event<GoToB> = sml::state<B>,
sml::state<From> + sml::event<GoToC> = sml::state<C>,
sml::state<From> + sml::event<GoToD> = sml::state<D>);
}
这个答案建议使用模板围绕库制作一个基本包装器,以使类似于 OP 的
auto make_common_transitions()
的功能可实现。
基本思想是定义一个自定义
TransitionTable
类,将传递给 sml::make_transition_table
的参数包装到 std::tuple
中:
#include <tuple>
#include <utility>
#include <https://raw.githubusercontent.com/boost-ext/sml/master/include/boost/sml.hpp>
namespace sml = boost::sml;
// framework
template<typename T>
concept cTransitionable = sml::concepts::transitional<T>().value;
struct SmlTableMaker {
template<typename... BasicTransitions>
constexpr auto operator()(BasicTransitions&&... basicTransitions) {
return sml::make_transition_table(std::forward<BasicTransitions>(basicTransitions)...);
}
};
template<cTransitionable... Transitions>
class TransitionTable {
public:
constexpr TransitionTable(Transitions const&... basicTransitions)
: _basicTransitions{ basicTransitions... }
{}
explicit constexpr TransitionTable(std::tuple<Transitions...> const& basicTransitions)
: _basicTransitions{ basicTransitions }
{}
// concatenate 2 TransitionTable.
template<typename... RhsTransitions>
constexpr TransitionTable<Transitions..., RhsTransitions...> operator+(TransitionTable<RhsTransitions...> const& rhs) const& {
return TransitionTable<Transitions..., RhsTransitions...>(std::tuple_cat(_basicTransitions, rhs.basicTransitions()));
}
// tuple containing all the basic sml transition rules.
constexpr std::tuple<Transitions...> const& basicTransitions() const& {
return _basicTransitions;
}
// generate a sml transition table from the stored basic transitions.
constexpr auto makeSmlTable() const& {
return std::apply(SmlTableMaker{}, _basicTransitions);
}
private:
std::tuple<Transitions...> _basicTransitions;
};
API 关键点:
TransitionTable + TransitionTable
连接 2 个表。TransitionTable::makeSmlTable()
创建 sml 的 API 所需的表。现在可以定义一次生成一个或多个基本规则的通用辅助函数:
// CTAD rules
template<cTransitionable... BasicTransitions>
TransitionTable(BasicTransitions...) -> TransitionTable<BasicTransitions...>;
// wraps a single sml transition rule into a TransitionTable
template<cTransitionable BasicTransition>
constexpr TransitionTable<BasicTransition> basicTransition(BasicTransition&& tr) {
return { std::forward<BasicTransition>(tr) };
}
// generates a TransitionTable with the rules 'src + event = destState' for each 'src' in 'srcStates'.
template<typename... SrcStates>
constexpr auto multiSourceTransition(std::tuple<SrcStates...> const& srcStates, auto event, auto destState) {
auto transformOne = [&destState,&event](auto src) {
return src + event = destState;
};
auto transformAll = [&transformOne](auto... src) {
return std::make_tuple(transformOne(src)...);
};
return TransitionTable{ std::apply(transformAll, srcStates) };
}
用途:
struct MyStateMachine {
struct A {};
struct B {};
struct C {};
struct D {};
static constexpr auto a = sml::state<A>;
static constexpr auto b = sml::state<B>;
static constexpr auto c = sml::state<C>;
static constexpr auto d = sml::state<D>;
struct GoToA {};
struct GoToB {};
struct GoToC {};
struct GoToD {};
struct LoopA {};
static constexpr auto multiSources = std::make_tuple(a,b,c,d);
constexpr auto operator()() const {
constexpr auto trTable = basicTransition(*a + sml::event<LoopA> = a)
+ multiSourceTransition(multiSources, sml::event<GoToA>, a)
+ multiSourceTransition(multiSources, sml::event<GoToB>, b)
+ multiSourceTransition(multiSources, sml::event<GoToC>, c)
+ multiSourceTransition(multiSources, sml::event<GoToD>, d);
return trTable.makeSmlTable();
}
};