I have the following state machine (sorry, I couldn't find how to make a smaller MRE):
- SM, containing MainSM, containing SubSM.
- SM has an internal transition table which says "ignore event Trigger".
- On start, the initial state of SM is MainSM, and the initial_state of MainSM is "Default" (so it is not SubSM).
- Only SubSM handles the "Trigger" event.
It works perfectly, and the state machine ignores the Trigger event as intended. However, if the on_entry method of MainSM sends the Trigger events then starting the state machine will not handle the event and call no_transition.
What is the problem? Is the SM not ready yet when calling start? Is this a bug or it is in-spec?
Here is the code snippet. Removing the process_event call line 80 and everything work.
#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;
}
Tested with GCC 5.5, Clang 8, Boost 1.58 and 1.73, with C++14.
During initialization of
SM, it will initialize the nestedMainSM. As part of the initialization it will sendInitEvent, which you handle to processTriggerEventon the containingSMinstance.IOW, You're processing an event on the
fsmparameter, which is the exact same instance as theSMsurrounding state machine that was in the process of being initialized.I think that's just not supported. Actually, the
Triggeris handled "just fine", but after youron_entryhandler exits,SMruns into trouble:That
self->internal_start(evt)doesn't work anymore because the outer SM was manipulated. The assertionindeed means there was no transition:
DOC FIND
In the documentation I stumbled on this wording which seems to confirm all of the above:
(emphasis mine)