I am using Boost MSM (basic and functor front-ends) and am trying to implement the following state machine:
In words:
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:
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.
usleep(useconds_t usec)
of unistd.h
)? My feeling is that pthreads, which I have not tried using with Boost.MSM, would be a more generic/less constrained implementation?create
and process
methods work (why does the create
function need a variadic template?). In particular, I've not previously worked with smart pointers or std::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).wp
and ios
member variables of Sm
would be great. What do you mean by using ios
pointer to intentionally meet copy constructor? I furthermore do not see ios
being set anywhere but in the constructor Sm(boost::asio::io_service* ios) : ios(ios) {}
, which it seems that you never call?State1_
front-end, you have three BOOST_STATIC_ASSERT
calls in the three on_entry
methods. What are these doing?main()
function, I was able to delete the line auto 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:
// g++ example.cpp -lboost_system
#include <iostream>
#include <boost/asio.hpp>
#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;
// ----- State machine
struct Sm : msmf::state_machine_def<Sm> {
using back = msm::back::state_machine<Sm>;
template <typename... T>
static std::shared_ptr<back> create(T&&... t) {
auto p = std::make_shared<back>(std::forward<T>(t)...);
p->wp = p; // set wp after creation.
return p;
}
template <typename Ev>
void process(Ev&& ev) {
// process_event via backend weak_ptr
wp.lock()->process_event(std::forward<Ev>(ev));
}
// ----- Events
struct EvSetParent {};
struct After2 {};
struct After5 {};
Sm(boost::asio::io_service* ios):ios(ios) {}
struct State1_:msmf::state_machine_def<State1_> {
template <class Event,class Fsm>
void on_entry(Event const&, Fsm& f) const {
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, Sm>::value));
std::cout << "State1::on_entry()" << std::endl;
f.process(EvSetParent());
}
struct Action {
template <class Event, class Fsm, class SourceState, class TargetState>
void operator()(Event const&, Fsm&, SourceState&, TargetState&) const {
std::cout << "Trying again..." << std::endl;
}
};
struct A:msmf::state<> {
template <class Event,class Fsm>
void on_entry(Event const&, Fsm& f) const {
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value));
std::cout << "A::on_entry()" << std::endl;
auto t = std::make_shared<boost::asio::deadline_timer>(*f.parent->ios);
t->expires_from_now(boost::posix_time::seconds(2));
t->async_wait([t, &f](boost::system::error_code const) {
f.parent->process(After2());
}
);
}
};
struct B:msmf::state<> {
template <class Event,class Fsm>
void on_entry(Event const&, Fsm& f) const {
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value));
std::cout << "B::on_entry()" << std::endl;
auto t = std::make_shared<boost::asio::deadline_timer>(*f.parent->ios);
t->expires_from_now(boost::posix_time::seconds(5));
t->async_wait([t, &f](boost::system::error_code const) {
f.parent->process(After5());
}
);
}
};
// Set initial state
typedef mpl::vector<A, B> initial_state;
// Transition table
struct transition_table:mpl::vector<
// Start Event Next Action Guard
msmf::Row < A, After2, A, Action, msmf::none >,
msmf::Row < B, After5, B, Action, msmf::none >
> {};
Sm* parent;
};
typedef msm::back::state_machine<State1_> State1;
// Set initial state
typedef State1 initial_state;
struct ActSetParent {
template <class Event, class Fsm, class SourceState, class TargetState>
void operator()(Event const&, Fsm& f, SourceState& s, TargetState&) const {
std::cout << "ActSetIos" << std::endl;
s.parent = &f; // set parent state machine to use process() in A and B.
}
};
// Transition table
struct transition_table:mpl::vector<
// Start Event Next Action Guard
msmf::Row < State1, EvSetParent, msmf::none, ActSetParent, msmf::none >
> {};
// front-end can access to back-end via wp.
std::weak_ptr<back> wp;
boost::asio::io_service* ios; // use pointer intentionally to meet copy constructible
};
int main() {
boost::asio::io_service ios;
auto t = std::make_shared<boost::asio::deadline_timer>(ios);
auto sm = Sm::create(&ios);
ios.post(
[&]{
sm->start();
}
);
ios.run();
}
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.
template <typename... T>
static std::shared_ptr<back> create(T&&... t) {
auto p = std::make_shared<back>(std::forward<T>(t)...);
p->wp = p; // set wp after creation.
return p;
}
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 wrapper process()
.
template <typename Ev>
void process(Ev&& ev) {
// process_event via backend weak_ptr
wp.lock()->process_event(std::forward<Ev>(ev));
}
Client code call the create() function as follows:
auto sm = Sm::create(&ios);
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.
typedef mpl::vector<A, B> initial_state;
State A and B is composite states. MSM uses sub-machine state to implement composite states. Outer most state Sm
is a state-machine and State1_
is also state machine. I set a timer in the entry action of state A and B. And when timer is fired, call process()
. However, processs()
is a member function of Sm
, not State1_
. So I need to implement some mechanism to access Sm
from Stete1_
.
I added the member variable parent
to State1_
. It's a pointer of Sm
. In the entry action of the State1_
, I call process()
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:
struct ActSetParent {
template <class Event, class Fsm, class SourceState, class TargetState>
void operator()(Event const&, Fsm& f, SourceState& s, TargetState&) const {
std::cout << "ActSetIos" << std::endl;
s.parent = &f; // set parent state machine to use process() in A and B.
}
};
Finally, I can call process()
in the action of the state A and B.
struct A:msmf::state<> {
template <class Event,class Fsm>
void on_entry(Event const&, Fsm& f) const {
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value));
std::cout << "A::on_entry()" << std::endl;
auto t = std::make_shared<boost::asio::deadline_timer>(*f.parent->ios);
t->expires_from_now(boost::posix_time::seconds(2));
t->async_wait([t, &f](boost::system::error_code const) {
f.parent->process(After2());
}
);
}
};
Edit
- 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) of unistd.h)? My feeling is that pthreads, which I have not tried using with Boost.MSM, would be a more generic/less constrained implementation?
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) from process_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.
- I am not clear on how the create and process methods work (why does the create function need a variadic template?). In particular, I've not previously worked with smart pointers or std::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).
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 is ios
. However, it could be changed depends on usecase. The function create()
creates a shared_ptr of back
. And back
's constructor forwards all parameters to frontend. So the argument ios at auto 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 change create()
function.
You can change the create()
function as follows:
static std::shared_ptr<back> create(boost::asio::io_service* ios) {
auto p = std::make_shared<back>(ios);
p->wp = p; // set wp after creation.
return p;
}
In addition, create()
and process()
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/forward
- In hand with 2, a better explanation of the purpose of the wp and ios member variables of Sm would be great. What do you mean by using ios pointer to intentionally meet copy constructor? I furthermore do not see ios being set anywhere but in the constructor Sm(boost::asio::io_service* ios) : ios(ios) {}, which it seems that you never call?
Boost.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:
static std::shared_ptr<back> create(boost::asio::io_service& ios) {
auto p = std::make_shared<back>(std::ref(ios));
p->wp = p; // set wp after creation.
return p;
}
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.
- Inside the State1_ front-end, you have three BOOST_STATIC_ASSERT calls in the three on_entry methods. What are these doing?
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.
- In the main() function, I was able to delete the line auto t = std::make_shared(ios); without changing the behaviour - was it redundant?
Aha, I forgot erase it. I update the code.
Here is the updated code:
#include <iostream>
#include <boost/asio.hpp>
#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;
// ----- State machine
struct Sm : msmf::state_machine_def<Sm> {
using back = msm::back::state_machine<Sm>;
static std::shared_ptr<back> create(boost::asio::io_service& ios) {
auto p = std::make_shared<back>(std::ref(ios));
p->wp = p; // set wp after creation.
return p;
}
template <typename Ev>
void process(Ev&& ev) {
// process_event via backend weak_ptr
wp.lock()->process_event(std::forward<Ev>(ev));
}
// ----- Events
struct EvSetParent {};
struct After2 {};
struct After5 {};
Sm(boost::asio::io_service& ios):ios(ios) {}
struct State1_:msmf::state_machine_def<State1_> {
template <class Event,class Fsm>
void on_entry(Event const&, Fsm& f) const {
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, Sm>::value));
std::cout << "State1::on_entry()" << std::endl;
f.process(EvSetParent());
}
struct Action {
template <class Event, class Fsm, class SourceState, class TargetState>
void operator()(Event const&, Fsm&, SourceState&, TargetState&) const {
std::cout << "Trying again..." << std::endl;
}
};
struct A:msmf::state<> {
template <class Event,class Fsm>
void on_entry(Event const&, Fsm& f) const {
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value));
std::cout << "A::on_entry()" << std::endl;
auto t = std::make_shared<boost::asio::deadline_timer>(f.parent->ios);
t->expires_from_now(boost::posix_time::seconds(2));
t->async_wait([t, &f](boost::system::error_code const) {
f.parent->process(After2());
}
);
}
};
struct B:msmf::state<> {
template <class Event,class Fsm>
void on_entry(Event const&, Fsm& f) const {
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value));
std::cout << "B::on_entry()" << std::endl;
auto t = std::make_shared<boost::asio::deadline_timer>(f.parent->ios);
t->expires_from_now(boost::posix_time::seconds(5));
t->async_wait([t, &f](boost::system::error_code const) {
f.parent->process(After5());
}
);
}
};
// Set initial state
typedef mpl::vector<A, B> initial_state;
// Transition table
struct transition_table:mpl::vector<
// Start Event Next Action Guard
msmf::Row < A, After2, A, Action, msmf::none >,
msmf::Row < B, After5, B, Action, msmf::none >
> {};
Sm* parent;
};
typedef msm::back::state_machine<State1_> State1;
// Set initial state
typedef State1 initial_state;
struct ActSetParent {
template <class Event, class Fsm, class SourceState, class TargetState>
void operator()(Event const&, Fsm& f, SourceState& s, TargetState&) const {
std::cout << "ActSetIos" << std::endl;
s.parent = &f; // set parent state machine to use process() in A and B.
}
};
// Transition table
struct transition_table:mpl::vector<
// Start Event Next Action Guard
msmf::Row < State1, EvSetParent, msmf::none, ActSetParent, msmf::none >
> {};
// front-end can access to back-end via wp.
std::weak_ptr<back> wp;
boost::asio::io_service& ios;
};
int main() {
boost::asio::io_service ios;
auto sm = Sm::create(ios);
ios.post(
[&]{
sm->start();
}
);
ios.run();
}