Hi,
I have a state machine that transitions from state s0 -> s2 upon receipt of one or more events, but there are two possible paths for reaching s2. If the context contains a set flag, the transition order must be s0 -> s1 -> s2 - in this case 2 identical events are received, each must trigger a state transition. If the context flag is not set, a single event will be received, which must cause a transition from s0 -> s2 in a single step.
I've found I can only get this to work if I define the transition_table rows in a specific order, the unconstrained s1 -> s2 transition row must be defined prior to the constrained rows that define s0 -> s1 and s1 -> s2. Here's the example code in compiler explorer (I'll also paste it below): https://godbolt.org/z/8h7P6dG6o I've tested this with Boost 1.91 also, result is the same.
Is there a way to make it work with the s1 -> s2 row defined last? I find that ordering to be more intuitive than the one that works.
#include <boost/msm/back11/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/euml/common.hpp>
#include <boost/msm/front/euml/operator.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/version.hpp>
#include <cstdio>
#include <cstdlib>
struct go_event {};
struct context
{
bool flagged = true;
};
struct is_flagged
{
template<typename Event, typename Fsm, typename SourceState, typename TargetState>
bool operator()(Event const&, Fsm& fsm, SourceState&, TargetState&)
{
return fsm.ctx.flagged;
}
};
using not_flagged = boost::msm::front::euml::Not_<is_flagged>;
struct s0 : boost::msm::front::state<> {};
struct s1 : boost::msm::front::state<> {};
struct s2 : boost::msm::front::state<> {};
using none = boost::msm::front::none;
// Working case: the s1 -> s2 unconditional transition is declared first
struct working_fsm_ : boost::msm::front::state_machine_def<working_fsm_>
{
context ctx;
bool no_transition_fired = false;
using initial_state = s0;
template<typename Start, typename Event, typename Next, typename Action, typename Guard>
using row = boost::msm::front::Row<Start, Event, Next, Action, Guard>;
struct transition_table
: boost::mpl::vector<
row<s1, go_event, s2, none, none>,
row<s0, go_event, s2, none, not_flagged>,
row<s0, go_event, s1, none, is_flagged>
>
{};
template<typename Event, typename Fsm>
void no_transition(Event const&, Fsm&, int)
{
no_transition_fired = true;
}
};
using working_fsm = boost::msm::back11::state_machine<working_fsm_>;
// Broken case: same as working, except s1 -> s2 transition is declared last,
// which is the more intuitive ordering
struct broken_fsm_ : boost::msm::front::state_machine_def<broken_fsm_>
{
context ctx;
bool no_transition_fired = false;
using initial_state = s0;
template<typename Start, typename Event, typename Next, typename Action, typename Guard>
using row = boost::msm::front::Row<Start, Event, Next, Action, Guard>;
struct transition_table
: boost::mpl::vector<
row<s0, go_event, s2, none, not_flagged>,
row<s0, go_event, s1, none, is_flagged>,
row<s1, go_event, s2, none, none>
>
{};
template<typename Event, typename Fsm>
void no_transition(Event const&, Fsm&, int)
{
no_transition_fired = true;
}
};
using broken_fsm = boost::msm::back11::state_machine<broken_fsm_>;
template<typename Fsm>
int current_state_id(Fsm const& fsm)
{
return *fsm.current_state();
}
int main()
{
std::printf("Boost version: %s\n", BOOST_LIB_VERSION);
// working_fsm: s0 -[flagged]-> s1 -> s2
{
working_fsm fsm;
fsm.start();
fsm.process_event(go_event{}); // expect s0 -> s1
int after_first = current_state_id(fsm);
fsm.process_event(go_event{}); // expect s1 -> s2
int after_second = current_state_id(fsm);
std::printf(
"[working] after 1st event: state_id=%d after 2nd event: state_id=%d "
"no_transition_fired=%d\n",
after_first,
after_second,
fsm.no_transition_fired);
if(fsm.no_transition_fired)
{
std::printf("[working] unexpected fail\n");
}
}
// --- broken_fsm: s0 -[flagged]-> s1 -> s1
{
broken_fsm fsm;
fsm.start();
fsm.process_event(go_event{}); // expect s0 -> s1
int after_first = current_state_id(fsm);
fsm.process_event(go_event{}); // expect s1 -> s2, but no_transition() is called
int after_second = current_state_id(fsm);
std::printf(
"[broken] after 1st event: state_id=%d after 2nd event: state_id=%d "
"no_transition_fired=%d\n",
after_first,
after_second,
fsm.no_transition_fired);
if(!fsm.no_transition_fired)
{
std::printf("[broken] Unexpected pass\n");
}
else
{
std::printf(
"[broken] Expected fail: transition from s1 -> s2 didn't happen\n");
}
}
}
Hi,
I have a state machine that transitions from state
s0 -> s2upon receipt of one or more events, but there are two possible paths for reachings2. If the context contains a set flag, the transition order must bes0 -> s1 -> s2- in this case 2 identical events are received, each must trigger a state transition. If the context flag is not set, a single event will be received, which must cause a transition froms0 -> s2in a single step.I've found I can only get this to work if I define the
transition_tablerows in a specific order, the unconstraineds1 -> s2transition row must be defined prior to the constrained rows that defines0 -> s1ands1 -> s2. Here's the example code in compiler explorer (I'll also paste it below): https://godbolt.org/z/8h7P6dG6o I've tested this with Boost 1.91 also, result is the same.Is there a way to make it work with the
s1 -> s2row defined last? I find that ordering to be more intuitive than the one that works.