今週は水曜日あたりまでBoost.MSM関係のドキュメントをチェックして、木曜日からおそるおそるコードを書き始めた。チェックしたドキュメントは下記。MSMで実装しようとするとほぼ必ず読み返している。
- Boost.MSMのドキュメント
- Boost.statechart / Boost.msm を実際に使うとハマるかもしれないポイントなど。
- Boost.Msm guide
- Boost.MSMの使い方
- redboltzの日記のMSMカテゴリ
Boost.MSMは状態とイベントを型定義で表現でする。これはメタ・プログラミングでは常道のやり方。値イコール型というやつだ。そしてその遷移をboost::mpl::vectorで遷移テーブルとして書いていく。大まかにはこれだけである。実際に書きかけのコードは以下。
struct Menu : msmf::state<> {};
struct Play_ : msmf::state_machine_def<Play_>
{
struct Init : msmf::state<> {};
struct Waiting : msmf::state<> {};
struct Active : msmf::state<>{};
struct LevelComplete : msmf::state<>{};
struct GameComplete : msmf::state<>{};
struct GameOver : msmf::state<>{};
struct Pause : msmf::state<>{};
struct Restart : msmf::state<>{};
struct CheckRestart : msmf::state<>{};
// 状態遷移テーブル
struct transition_table : boost::mpl::vector
// 現在状態 ,イベント , 次の状態 , アクション , ガード
<
msmf::Row <Init ,ev::Complete ,Waiting ,msmf::none,msmf::none>,
msmf::Row <Waiting ,ev::StartGame ,Active ,msmf::none, msmf::none > ,
msmf::Row <Active ,ev::Escape ,Pause ,msmf::none, msmf::none > ,
msmf::Row <Active ,ev::PlayerIsGone ,CheckRestart ,msmf::none, msmf::none > ,
// msmf::Row <CheckRestart ,ev::st::any ,Restart ,msmf::none, msmf::none > ,
msmf::Row <CheckRestart ,ev::PlayerLeftZero ,GameOver ,msmf::none, msmf::none > ,
msmf::Row <Pause ,ev::Exit ,Waiting ,msmf::none, msmf::none > ,
msmf::Row <Pause ,ev::Escape ,Active ,msmf::none, msmf::none > ,
msmf::Row <GameOver ,ev::Escape ,Waiting ,msmf::none, msmf::none > ,
msmf::Row <GameOver ,ev::TimeOver ,Waiting ,msmf::none, msmf::none >
>
{};
typedef Init initial_state;
};
typedef boost::msm::back::state_machine< Play_ > Play;
struct Edit : msmf::state<> {};
struct game_main_ : msmf::state_machine_def<game_main_>
{
public:
game_main_(){};
virtual ~game_main_(){};
// 状態遷移テーブル
struct transition_table : boost::mpl::vector
// 現在状態 ,イベント ,次の状態 , アクション , ガード
<
msmf::Row <Menu ,ev::SelectEdit ,Edit ,msmf::none ,msmf::none > ,
msmf::Row <Menu ,ev::SelectPlay ,Play ,msmf::none ,msmf::none >
>
{};
typedef Menu initial_state;
};
typedef boost::msm::back::state_machine< game_main_ > game_main;
コードを見ればわかると思うけれども、いくつか実装のルールがある。例えば
- 状態を表す型はboost::msm::front::stateを継承する。
- 状態を管理する型はboost::msm::front::state_machine_defを継承する。
- 初期状態はinitial_stateをtypedefする。
- 遷移テーブルはtransition_tableという型をboost::mpl::vectorを継承して定義する。boost::mpl::vectorのテンプレートパラメータに遷移テーブルを書く。
- 状態管理クラスは「typedef boost::msm::back::statemachine<状態管理クラス定義> 状態管理クラス」としてtypedefした型名を使う。
ほかにもいろいろあるが、それはドキュメントをみればわかる。上記の例を見るとわかるように状態管理クラスは入れ子にできる。こんな感じで実装していくのである。
問題となるのはコンパイル速度である。もうすでにその影響は出ている。普通この手のものはヘッダーに定義するとのちのち痛い目にあう。コンパイルが遅いのでちょっと作ってはコンパイルしてみて試すということがきつくなってくるのだ。なので今のうちにpimpl化してヘッダーには状態管理をラップするクラスと、イベントクラスの定義のみ含むようにした。これで実装を続けていく。