【问题标题】:Starting state machine cannot handle internal transition on startup启动状态机无法处理启动时的内部转换
【发布时间】:2020-08-29 22:51:15
【问题描述】:

我有以下状态机(抱歉,我找不到如何制作更小的 MRE):

  • SM,包含 MainSM,包含 SubSM。
  • SM 有一个内部转换表,上面写着“忽略事件触发器”。
  • 启动时,SM 的初始状态为 MainSM,MainSM 的 initial_state 为“Default”(因此它不是 SubSM)。
  • 只有 SubSM 处理“触发器”事件。

它运行良好,状态机按预期忽略Trigger 事件。 但是,如果MainSMon_entry方法发送Trigger事件,那么启动状态机将不会处理该事件并调用no_transition

有什么问题?调用start时SM还没准备好?这是一个错误还是符合规范?

这里是代码 sn-p。 删除 process_event 调用第 80 行,一切正常。

#include <iostream>

#include <boost/core/demangle.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/functor_row.hpp>

#define ON_ENTRY_LOG_NAME(name) \
    template <class Event, class FSM>                   \
    void on_entry(const Event &, FSM&) {            \
        std::cout << "Entering " #name << std::endl;    \
    }
#define ON_EXIT_LOG_NAME(name) \
    template <class Event, class FSM> \
    void on_exit(const Event &, FSM&) { \
        std::cout << "Exitting " #name << std::endl;    \
    }


namespace  // Concrete FSM implementation
{

    namespace msm = boost::msm;
    namespace msmb = boost::msm::back;
    namespace msmf = boost::msm::front;
    namespace mpl = boost::mpl;

    // events
    struct Stop {};
    struct Recover {};
    struct Start {};
    struct Trigger {};

    struct SubSM_front: msmf::state_machine_def<SubSM_front>
    {
        struct Fidgetting: msmf::state<>
        {
            ON_ENTRY_LOG_NAME(Fidgetting);
            ON_EXIT_LOG_NAME(Fidgetting);
        };

        struct FidgettingCompulsively: msmf::state<>
        {
            ON_ENTRY_LOG_NAME(FidgettingCompulsively);
            ON_EXIT_LOG_NAME(FidgettingCompulsively);
        };

        using initial_state = Fidgetting;

        struct transition_table: mpl::vector<
            msmf::Row<Fidgetting,             Trigger, FidgettingCompulsively>,
            msmf::Row<FidgettingCompulsively, Trigger, Fidgetting>
        > {};

        ON_ENTRY_LOG_NAME(SubSM);
        ON_EXIT_LOG_NAME(SubSM);
    };

    using SubSM = msmb::state_machine<SubSM_front>;

    struct MainSM_front: msmf::state_machine_def<MainSM_front>
    {
        struct Default: msmf::state<>
        {
            ON_ENTRY_LOG_NAME(Default);
            ON_EXIT_LOG_NAME(Default);
        };

        using initial_state = Default;

        struct transition_table: mpl::vector<
            msmf::Row<Default, Start, SubSM>
        > {};

        template <class Event, class FSM>
        void on_entry(const Event &, FSM &fsm)
        {
            std::cout << "Entering MainSM" << std::endl;
            // This line make a call to no_transition
            fsm.process_event(Trigger{});
        }

        ON_EXIT_LOG_NAME(MainSM);
    };

    using MainSM = msmb::state_machine<MainSM_front>;

    struct SM_front: msmf::state_machine_def<SM_front>
    {
        struct Stopped: msmf::state<>
        {
            ON_ENTRY_LOG_NAME(Stopped);
            ON_EXIT_LOG_NAME(Stopped);
        };

        using initial_state = MainSM;

        using transition_table = mpl::vector<
            msmf::Row<MainSM,  Stop,    Stopped>,
            msmf::Row<Stopped, Recover, MainSM>
        >;

        using internal_transition_table = mpl::vector<
            msmf::Internal<Trigger>
        >;

        ON_ENTRY_LOG_NAME(SM);
        ON_EXIT_LOG_NAME(SM);
    };

    using SM = msmb::state_machine<SM_front>;

    void test()
    {
        SM sm;

        sm.start();
        sm.process_event(Trigger{});
        sm.stop();
    }
}

int main()
{
    test();
    return 0;
}

使用 GCC 5.5、Clang 8、Boost 1.58 和 1.73 以及 C++14 进行测试。

【问题讨论】:

    标签: c++ boost boost-msm


    【解决方案1】:

    SM的初始化过程中,它会初始化嵌套的MainSM。作为初始化的一部分,它将发送InitEvent,您可以处理它以处理包含SM 实例的TriggerEvent

    IOW,您正在处理 fsm 参数上的事件,该参数与正在初始化的 SM 周围状态机完全相同。

    我认为这是不支持的。实际上,Trigger 处理得“很好”,但是在您的 on_entry 处理程序退出后,SM 遇到了麻烦:

     struct direct_event_start_helper
     {
         direct_event_start_helper(library_sm* self_):self(self_){}
         // this variant is for the standard case, entry due to activation of the containing FSM
         template <class EventType,class FsmType>
         typename ::boost::disable_if<typename has_direct_entry<EventType>::type,void>::type
             operator()(EventType const& evt,FsmType& fsm, ::boost::msm::back::dummy<0> = 0)
         {
             (static_cast<Derived*>(self))->on_entry(evt,fsm);
             self->internal_start(evt);
         }
    

    self-&gt;internal_start(evt) 不再起作用,因为外部 SM 被操纵了。断言

    sotest: boost_1_72_0/boost/msm/front/state_machine_def.hpp:203: void boost::msm::front::state_machine_def<Derived, BaseState>::no_transition(const Event&, FSM&, int) [with FSM = boost::msm::back::state_machine<{anonymous}::MainSM_front>; Event = {anonymous}::Trigger; Derived = {anonymous}::MainSM_front; BaseState = boost::msm::front::default_base_state]: Assertion `false' failed.
    

    确实意味着没有过渡:

    template <class FSM,class Event>
    void no_transition(Event const& ,FSM&, int )
    {
        BOOST_ASSERT(false);
    }
    

    文档查找

    在文档中,我偶然发现了这个措辞,似乎证实了上述所有内容:

    注意:您可能已经注意到本教程在创建后立即在状态机上调用 start()。 start 方法将启动状态机,这意味着它将激活初始状态,这意味着将依次调用初始状态的进入行为。我们需要这个的原因将在back-end part 中解释。 调用 start 后,状态机准备好处理事件。同样,调用 stop() 将导致调用最后的退出操作。

    强调我的

    【讨论】:

      猜你喜欢
      • 2022-09-23
      • 1970-01-01
      • 2016-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-20
      • 1970-01-01
      相关资源
      最近更新 更多