如何减少有限状态机转换表中类似转换的重复?

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

我正在使用 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>);
}
c++ boost state-machine boost-sml
1个回答
0
投票

这个答案建议使用模板围绕库制作一个基本包装器,以使类似于 OP 的

auto make_common_transitions()
的功能可实现。

  • 为了方便起见,它使用 C++20。如有必要,它应该可以向后移植到 C++14。
  • 实现移动语义和完美转发留给读者。

基本思想是定义一个自定义

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();
    }
};

>> 现场演示(godbolt)

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