コルーチンを使って、お手軽なステートマシンを作ってみました。
ステートマシンというと、Boost.MSMをはじめ色々ライブラリーがあり、いまさらな感じなのですが、
-
重かったり(ビルド時間が)
-
面倒くさかったり(コードジェネレーターを使うのが)
するので、そんな大げさなことをしたくないとき用です。
# include <iostream>
# include <boost/assert.hpp>
# include <boost/asio/coroutine.hpp>
constexpr int ANY_EVENT = ~0,
EVENT1 = 1 << 1,
EVENT2 = 1 << 2,
EVENT3 = 1 << 3;
enum state_t {
STATE1 = 1,
STATE2,
};
# include <boost/asio/yield.hpp>
template <typename Action>
class state_machine
{
public:
state_machine()
: ctx_(), state_(), mask_(ANY_EVENT), action_()
{
do_process_event(0);
}
void process_event(int event)
{
if (event & mask_)
do_process_event(event);
else
action_.on_error(state_, event);
}
bool is_complete() const
{
return ctx_.is_complete();
}
private:
void do_process_event(int event)
{
reenter(ctx_) {
while (1) {
yield accept_event(STATE1, EVENT1 | EVENT2);
if (event == EVENT2) {
action_.do_action2();
break;
}
else {
action_.do_action1();
}
}
yield accept_event(STATE2, EVENT3);
action_.do_action3();
}
}
void accept_event(state_t state, int mask) {
if (state != state_) {
state_t tmp = state_;
state_ = state;
action_.on_state_changed(tmp, state);
}
mask_ = mask;
}
boost::asio::coroutine ctx_;
state_t state_;
int mask_;
Action action_;
};
# include <boost/asio/unyield.hpp>
//-------------------------------------------------------------------
// BEGIN TEST CODE
using namespace std;
int check = 0;
struct dummy_action {
void on_state_changed(state_t before, state_t after) { cout << "State changed: " << before << " -> " << after << endl; }
void do_action1() { check = 1; cout << "Action 1 is called" << endl; }
void do_action2() { check = 2; cout << "Action 2 is called" << endl; }
void do_action3() { check = 3; cout << "Action 3 is called" << endl; }
void on_error(state_t state, int event) { cout << state << ": Event " << event << " is ignored." << endl; }
};
int main() {
state_machine<dummy_action> sm;
check = 0;
sm.process_event(EVENT1);
BOOST_ASSERT(check == 1);
check = 0;
sm.process_event(EVENT3);
BOOST_ASSERT(check == 0);
check = 0;
sm.process_event(EVENT1);
BOOST_ASSERT(check == 1);
check = 0;
sm.process_event(EVENT2);
BOOST_ASSERT(check == 2);
check = 0;
sm.process_event(EVENT3);
BOOST_ASSERT(check == 3);
BOOST_ASSERT(sm.is_complete() == true);
return 0;
}
ビルド方法
g++ -std=c++11 -I$BOOST_ROOT state_machine_demo.cpp
実行結果
State changed: 0 -> 1
Action 1 is called
1: Event 8 is ignored.
Action 1 is called
Action 2 is called
State changed: 1 -> 2
Action 3 is called