Reputation: 4982
I have a real-time loop and I need to do work each iteration depending on which state I'm in.
I am using Boosts Meta State Machine library to implement my state machine. What's the best way to call a function based on which state I'm in?
Let's say that I have a super-simple state machine:
//Events
struct run {};
struct stop {};
struct MyStateMachine : public boost::msm::front::state_machine_def<MyStateMachine>
{
// FSM states
struct Idle : public boost::msm::front::state<> {};
struct Running : public boost::msm::front::state<> {};
typedef Idle initial_state;
// transition actions
void do_start(run const&) { std::cout << "Starting\n"; }
void do_stop(stop const&) { std::cout << "Stopping\n"; }
struct transition_table : boost::mpl::vector<
// Start Event Next Action
a_row < Idle , run , Running, &MyStateMachine::do_start >,
a_row < Running , stop , Idle , &MyStateMachine::do_stop >
> {};
};
I can see a few ways to do work based on which state we are in:
1) switch on the current state
class MyModule : RealTimeModule
{
void Init() override { // Called before realtime
fsm_.start();
}
void Step() override { // Called once each iteration during realtime
switch(fsm_.current_state()[0])
{
case 0: do_idle(); break;
case 1: do_run(); break;
default: assert();
}
}
void DoEvent(Event e) override { // Called once per received event
fsm_.process_event(e);
}
private:
boost::msm::back::state_machine<MyStateMachine> fsm_;
};
This doesn't seem great because there is no intuitive way to know that case 0
is Idle or that case 1
is Running.
2) Add transitions and events to represent each iteration
//Events
struct run {};
struct stop {};
struct step {}; // !!!NEW EVENT!!!
struct MyStateMachine : public boost::msm::front::state_machine_def<MyStateMachine>
{
...
// transition actions
void do_start(run const&) { std::cout << "Starting\n"; }
void do_stop(stop const&) { std::cout << "Stopping\n"; }
void do_idle(step const&) { std::cout << "Idling \n"; } //!!!NEW ACTION!!!
void do_run (step const&) { std::cout << "Running \n"; } //!!!NEW ACTION!!!
struct transition_table : boost::mpl::vector<
// Start Event Next Action
a_row < Idle , run , Running, &MyStateMachine::do_start >,
a_row < Running , stop , Idle , &MyStateMachine::do_stop >,
a_row < Idle , step , Idle , &MyStateMachine::do_idle >, //!!!NEW TRANSITION!!!
a_row < Running , step , Running, &MyStateMachine::do_step > //!!!NEW TRANSITION!!!
> {};
};
class MyModule : RealTimeModule
{
...
void Step() override {
fsm_.process_event(step); //!!!SIMPLIFIED STEP!!!
}
...
};
This seems a little better, but my issue here is that if I want to implement boost::msm::front::state<>::on_entry()
or on_exit
, then those functions will be called each iteration instead of on a transition to that state.
I've found that it is possible to call an action with an event without a transition using the functor front-end as used here. The row in the transition table would look like this:
msm::front::Row < Idle, step, msm::front::none, step_idle, msm::front::none >
However, in order to use that, I need to make my actions looks like this:
struct do_step {
template <class Event, class Fsm, class SourceState, class TargetState>
void operator()(Event const&, Fsm&, SourceState&, TargetState&) const {
std::cout << "Idling" << std::endl;
}
};
Perhaps it's a style thing, but that seems wayyy too verbose to be a good solution. Is there a way to add something like msm::front::none
by using the simple front-end instead of the functor front-end?
Upvotes: 2
Views: 1411
Reputation: 4982
Ok I found an answer. It irks me that I really need to read the uncommented boost headers instead of documentation or references to figure out what to do.
I needed to use the a_irow
structure which does not involve a transition, but rather just calls the action:
struct transition_table : boost::mpl::vector<
// Start Event Next Action
a_row < Idle , run , Running, &MyStateMachine::do_start >,
a_row < Running , stop , Idle , &MyStateMachine::do_stop >,
a_irow < Idle , step , &MyStateMachine::do_idle >,
a_irow < Running , step , &MyStateMachine::do_step >
> {};
In summary these are the types of rows that can be added:
a_row<Source, Event, Target, Action >
_row<Source, Event, Target >
row<Source, Event, Target, Action, Guard>
g_row<Source, Event, Target, Guard>
a_irow<Source, Event, , Action >
irow<Source, Event, Action, Guard>
g_irow<Source, Event, Guard>
_irow<Source, Event, > // Forces events to be ignored
With:
typename Source;
class Event;
typename Target;
typedef void (Derived::*action)(Event const&) Action;
typedef bool (Derived::*guard)(Event const&) Guard;
Reference: boost/msm/front/state_machine_def.hpp
Upvotes: 2