読者です 読者をやめる 読者になる 読者になる

Boost.statechart で複数状態を持つマシンを実装する。

C++

 Boost.statechart で複数の状態を持つマシンを実装したコードを載せるとか載せないとか言ってましたが、風邪でダウンしてあまりにも暇になったので載せることにします。
 こんなんですね。

状態遷移は全部書くと面倒なので、オレンジの矢印のみを実装します。

#include <iostream>
#include <boost/statechart/simple_state.hpp>
#include <boost/statechart/state_machine.hpp>
#include <boost/statechart/transition.hpp>
#include <boost/statechart/event.hpp>
#include <boost/mpl/list.hpp>

using namespace std;
namespace sc = boost::statechart;

namespace states
{
	struct main;
	
	struct on;
	struct off;
	
	struct enable;
	struct disable;
	
	struct mode1;
	struct mode2;
	struct mode3;
}

struct machine : sc::state_machine< machine, states::main >
{
	machine() { initiate(); }
	
	void print_states();
};

namespace events
{
	struct go_to_next_mode : sc::event< go_to_next_mode > {};
}

namespace states
{
	struct    main : sc::simple_state< main, machine,
						 boost::mpl::list< on, enable, mode1 > >
	{};
	
	struct      on : sc::simple_state<      on, main::orthogonal< 0 > > {};
	struct     off : sc::simple_state<     off, main::orthogonal< 0 > > {};
	
	struct  enable : sc::simple_state<  enable, main::orthogonal< 1 > > {};
	struct disable : sc::simple_state< disable, main::orthogonal< 1 > > {};
	
	struct   mode1 : sc::simple_state<   mode1, main::orthogonal< 2 > >
		{ typedef sc::transition< events::go_to_next_mode, mode2 > reactions; };
	struct   mode2 : sc::simple_state<   mode2, main::orthogonal< 2 > >
		{ typedef sc::transition< events::go_to_next_mode, mode3 > reactions; };
	struct   mode3 : sc::simple_state<   mode3, main::orthogonal< 2 > >
		{ typedef sc::transition< events::go_to_next_mode, mode1 > reactions; };
}

void machine::print_states()
{
	for( state_iterator i = state_begin(); i != state_end(); ++ i )
	{
		#define print_if_same_type( state_name )			\
			if( typeid( * i ) == typeid( states::state_name ) ) {	\
				cout << #state_name << " ";			\
			}
		
		print_if_same_type( on );
		print_if_same_type( off );
		print_if_same_type( enable );
		print_if_same_type( disable );
		print_if_same_type( mode1 );
		print_if_same_type( mode2 );
		print_if_same_type( mode3 );
	}
	cout << endl;
}

int main()
{
	machine the_machine;
		
	the_machine.print_states();
	
	the_machine.process_event( events::go_to_next_mode() );
	
	the_machine.print_states();
	
	the_machine.process_event( events::go_to_next_mode() );
	
	the_machine.print_states();
}

出力

on enable mode1 
on enable mode2
on enable mode3

 この記述で注目すべきは次の 2 つです。

1. states::main の定義
 sc::simple_state< ○, ○, mpl::list<> > となってます。第 3 引数は子状態の初期値を指定するところですがここに mpl::list で子の状態を列記することで「複数の状態を持つ」ことを表現しています。

2. states::on の定義
 sc::simple_state<> の第 2 引数は親コンテキストを指定するところですが、ここに main::orthogonal< 0 > と書いてあります。1. のような記述があったとき、その何番目に属するのか、というのを orthogonal で指定しています。ここで明示的に指定することで、例えば on から mode3 に遷移するような誤ったコードを記述してしまった場合でもコンパイルエラーとすることができるようになっています。

 ところで、複数の状態を同時に持ちたいだけなら、そもそもこんな機能は Boost.statechart には不要ですよね。なぜなら状態マシンを複数定義して、それぞれインスタンス化してやれば要求は満たせるからです。
 じゃーこの機能は一体何に使うかというと、現在の状態の組み合わせを状態遷移の条件に使うときに使います。

 やってみましょ。上の例に、状態遷移を追加します。
・toggle_on_off イベントを追加します。
・toggle_on_off イベントを受け取ったら on - off をトグルします。
・但し on で enable で mode3 の場合だけは、on のままで disable にします。

// 新しくイベント toggle_on_off を追加
namespace events
{
	struct toggle_on_off : sc::event< toggle_on_off > {};
}

// main に custom_reaction を追加。
namespace states
{
	
	struct main : sc::simple_state< main, machine,
						boost::mpl::list< on, enable, mode1 > >
	{
		typedef sc::custom_reaction< events::toggle_on_off > reactions;
		
		sc::result react( events::toggle_on_off const & )
		{
			bool const is_on     = !! state_downcast<     on const * >();
			bool const is_enable = !! state_downcast< enable const * >();
			bool const is_mode3  = !! state_downcast<  mode3 const * >();
			if( is_on ) {
				if( is_enable && is_mode3 ) {
					return transit< disable >();
				} else {
					return transit< off >();
				}
			} else {
				return transit< on >();
			}
		}
	};
}

// toggle_on_off イベントを渡してみる。
int main()
{
	machine the_machine;
		
	the_machine.print_states();
	
	the_machine.process_event( events::go_to_next_mode() );
	
	the_machine.print_states();
	
	the_machine.process_event( events::go_to_next_mode() );
	
	the_machine.print_states();
	
	the_machine.process_event( events::toggle_on_off() );
	
	the_machine.print_states();
}

出力

on enable mode1 
on enable mode2
on enable mode3
on disable mode1

 toggle_on_off イベントが適切に受理されて disable に遷移しました。

※ state_downcast は現在の状態が、型引数で指定したものであれば 非NULL を返します。違ってれば NULL を返します。
※ state_downcast の型引数を参照型にするとキャスト失敗時に bad_cast を throw します。