I am using Boost MSM (basic and functor front-ends) and am trying to implement the following state machine:
In words:
- Enter state State1
- Enter state A and execute action_A. After 2 seconds, print "Trying again..." and re-execute state A (i.e. call its entry action). This loops forever...
- At the same time as 2 (i.e. "in parallel"), enter state B and execute action_B. After 5 seconds, print "Trying again..." and re-execute state B (i.e. call its entry action). This loops forever...
I would like to know the way to create this state machine in Boost MSM. There are two tricks here which I cannot figure out how to do:
- Execution in parallel (i.e. running action_A does not stop running action_B at the same time)
- Delayed transition (i.e. the transitions which happen after 2 seconds for state A and after 5 seconds for state B). Neither delay should be blocking! The transitions should just "fire" after this time.
Thank you very much for helping.
Edit
@TakatoshiKondo answer does what I need, but I'd like to have more explanation on certain parts of the answer in order to understand it fully.
- How does this compare with a pthreads implementation? Do you think Boost.Asio is a better solution than putting the states A and B into different threads and having blocking, passive waits in each (such as what could be achieved via
usleep(useconds_t usec)
ofunistd.h
)? My feeling is that pthreads, which I have not tried using with Boost.MSM, would be a more generic/less constrained implementation? - I am not clear on how the
create
andprocess
methods work (why does thecreate
function need a variadic template?). In particular, I've not previously worked with smart pointers orstd::forward
, so if you could give a human explanation of each line in these functions it would be great (I'm short on time to read about these features generically in order to try to understand this code). - In hand with 2, a better explanation of the purpose of the
wp
andios
member variables ofSm
would be great. What do you mean by usingios
pointer to intentionally meet copy constructor? I furthermore do not seeios
being set anywhere but in the constructorSm(boost::asio::io_service* ios) : ios(ios) {}
, which it seems that you never call? - Inside the
State1_
front-end, you have threeBOOST_STATIC_ASSERT
calls in the threeon_entry
methods. What are these doing? - In the
main()
function, I was able to delete the lineauto t = std::make_shared<boost::asio::deadline_timer>(ios);
without changing the behaviour - was it redundant?
Here is a complete code example to do that:
Let's digging the code.
Boost.MSM doesn't support delayed event fire mechanism. So we need to some timer handling mechanism. I choose Boost.Asio deadline timer. It works well with event driven library such as Boost.MSM.
In order to call process_event() in the front-end of the state machine, it needs to know its back-end. So I wrote
create()
function.It creates a shared_ptr of the back-end and then, and assigns it to the weak_ptr. If the weak_ptr set correctly, then I can call
process_event()
as follows. I wrote a wrapperprocess()
.Client code call the create() function as follows:
Sm has the member variable ios to set deadline timer. The front-end of the state-machine is required copyable by MSM. So ios is the pointer of io_service not reference.
State A and B are orthogonal regions. In order to implement orthogonal regions, define multiple initial states as mpl::vector.
State A and B is composite states. MSM uses sub-machine state to implement composite states. Outer most state
Sm
is a state-machine andState1_
is also state machine. I set a timer in the entry action of state A and B. And when timer is fired, callprocess()
. However,processs()
is a member function ofSm
, notState1_
. So I need to implement some mechanism to accessSm
fromStete1_
. I added the member variableparent
toState1_
. It's a pointer ofSm
. In the entry action of theState1_
, I callprocess()
and the event is PEvSetParent. It simply invokes
ActSetParent. In the action, SourceState is
State1_`. I set parent member variable to parent pointer as follows:Finally, I can call
process()
in the action of the state A and B.Edit
Boost.MSM's
process_event()
is NOT thread-safe. So you need to lock it. See Thread safety in Boost msm AFAIK, sleep()/usleep()/nanosleep() are blocking functions. When you call them in the action of Boost.MSM, that means they are called (ogirinally) fromprocess_event()
. And it requires lock. Finally, blocking wait blocks each other (in this case, after2 and after5). Hence I think that Boost.ASIO's async approch is better.Boost.MSM's backend inherits its frontend. The frontend constructor is
Sm(boost::asio::io_service* ios):ios(ios) {}
. In this case, the parameter of the constructor isios
. However, it could be changed depends on usecase. The functioncreate()
creates a shared_ptr ofback
. Andback
's constructor forwards all parameters to frontend. So the argument ios atauto sm = Sm::create(&ios);
is forwarded to Sm's constructor. The reason I use variadic templates and std::forward is maximize flexibility. If the parameters of Sm's constructor is changed, I don't need to changecreate()
function. You can change thecreate()
function as follows:In addition,
create()
andprocess()
use template parameters that with&&
. They are called as forwarding-reference (universal-reference). It is an idiom called perfect-forwarding. See http://en.cppreference.com/w/cpp/utility/forwardBoost.MSM doesn't support forwarding-reference, so far. I wrote a pull request See https://github.com/boostorg/msm/pull/8
So forwarding-reference invokes copy-constructor in the Boost.MSM. That is the reason I choose the pointer of boost::asio::io_service. However, it is not an essential point of the original question. If I don't use forwarding-reference, I can use the reference types in
Sm
. So I update the code as follows:std::ref
is not for make_shared. It is for Boost.MSM. Boost.MSM's constructor requires specifying reference or not due to lack of the forwarding reference support.It does nothing in the run-time. Just cheking the type of Fsm at the compile-time. Sometimes I got confused the type of Fsm. I guess the readers also might get confused, so I leave it in the code.
Aha, I forgot erase it. I update the code.
Here is the updated code: