I have a state machine with states A
, B
and C
. C
handles event e
directly, while A
and B
do not, but I want to fallback to a default handler (what Samek calls the "ultimate hook" pattern) for event e
(that will be called when no handler is found in states A
and B
). However, with Boost MSM, I cannot implement this handler at the state machine level, but must introduce an additional container state S
containing A
, B
and C
.
Is there a way to implement event handlers directly at the state machine level itself instead of needing this container state?
You can do that using no_transition
handler.
Let's say the event E is defined as follows:
struct E {
int val = 42;
};
Here is an example of no_transition handler for event E:
template <typename Fsm>
void no_transition(E const& e, Fsm&, int state) {
std::cout << "no transition at state " << state << " event e.val" << e.val << std::endl;
}
However, Boost.MSM requires no_transition handler for other types due to meta-programming.
So you need to define the no_transition handler for other events as follows:
template <typename Fsm, typename Event>
void no_transition(Event const&, Fsm& ,int) {
std::cout << "shouldn't be called but need to define to compile" << std::endl;
}
You might think that you can do as follows:
template <typename Fsm, typename Event>
void no_transition(Event const&, Fsm& ,int) {
std::cout << "no transition at state " << state << " event e.val" << e.val << std::endl;
}
It doesn't work because the no_transition handler instantiates not only E but also other event that doesn't have the member variable val
.
Here is an example of whole working code based on your question:
#include <iostream>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/functor_row.hpp>
namespace msm = boost::msm;
namespace msmf = boost::msm::front;
namespace mpl = boost::mpl;
// ----- Events
struct E {
int val = 42;
};
struct AtoB {};
struct BtoC {};
// ----- State machine
struct Sm1_:msmf::state_machine_def<Sm1_>
{
// States
struct A:msmf::state<> {
template <class Event,class Fsm>
void on_entry(Event const&, Fsm&) {
std::cout << "A::on_entry()" << std::endl;
}
};
struct B:msmf::state<> {
template <class Event,class Fsm>
void on_entry(Event const&, Fsm&) {
std::cout << "B::on_entry()" << std::endl;
}
};
struct C:msmf::state<> {
template <class Event,class Fsm>
void on_entry(Event const&, Fsm&) {
std::cout << "C::on_entry()" << std::endl;
}
};
// Set initial state
using initial_state = A;
// Transition table
struct transition_table:mpl::vector<
// Start Event Next Action Guard
msmf::Row < A, AtoB, B, msmf::none, msmf::none >,
msmf::Row < B, BtoC, C, msmf::none, msmf::none >,
msmf::Row < C, E, A, msmf::none, msmf::none >
> {};
template <typename Fsm>
void no_transition(E const& e, Fsm&, int state) {
std::cout << "no transition at state " << state << " event e.val" << e.val << std::endl;
}
template <typename Fsm, typename Event>
void no_transition(Event const&, Fsm& ,int) {
std::cout << "shouldn't be called but need to define to compile" << std::endl;
}
};
// Pick a back-end
using Sm1 = msm::back::state_machine<Sm1_>;
int main() {
Sm1 sm1;
sm1.start();
std::cout << "> Send Event E" << std::endl;
sm1.process_event(E());
std::cout << "> Send Event AtoB" << std::endl;
sm1.process_event(AtoB());
std::cout << "> Send Event E" << std::endl;
sm1.process_event(E());
std::cout << "> Send Event BtoC" << std::endl;
sm1.process_event(BtoC());
std::cout << "> Send Event E" << std::endl;
sm1.process_event(E());
}