Начальный конечный автомат не может обрабатывать внутренний переход при запуске

У меня есть следующий конечный автомат (извините, я не смог найти, как сделать MRE меньшего размера):

  • SM, содержащий MainSM, содержащий SubSM.
  • SM имеет внутреннюю таблицу переходов, в которой указано «игнорировать триггер события».
  • При запуске начальное состояние SM — MainSM, а начальное_состояние MainSM — «Default» (поэтому это не SubSM).
  • Только SubSM обрабатывает событие "Trigger".

Он работает отлично, и конечный автомат игнорирует событие Trigger, как и предполагалось. Однако, если метод on_entry MainSM отправляет события Trigger, то запуск конечного автомата не будет обрабатывать событие и вызовет no_transition.

В чем проблема? SM еще не готов к началу вызова? Это баг или так в спецификации?

Вот фрагмент кода. Убираем строку вызова 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.


person Hugal31    schedule 13.05.2020    source источник


Ответы (1)


Во время инициализации SM будет инициализирован вложенный MainSM. В рамках инициализации он отправит InitEvent, который вы обрабатываете для обработки TriggerEvent в содержащем экземпляре SM.

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->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 инициирует конечный автомат, то есть активирует начальное состояние, что, в свою очередь, означает, что будет вызвано поведение при входе в начальное состояние. Причина, по которой нам это нужно, будет объяснена в внутренняя часть. После вызова start конечный автомат готов к обработке событий. Точно так же вызов stop() приведет к вызову последних действий выхода.

(выделено мной)

person sehe    schedule 13.05.2020