プログラマならみんな使いたいステートマシン。
ライブラリもいろいろありますが、重かったり、大人の事情で使えなかったり。
難しいコトは考えずに書いてみましょう。
チェックポイントとしては、
- C言語で書く
- 呼び出し時のオーバーヘッドは小さく
- Entry, Exit のイベントを使いたい
- コードが汚くならない ←
以下サンプル。
#include <stdio.h>
#define PRINT(x) printf(#x "\n")
#define DUMP(x) printf("\tsignal=%d param=%d\n",x->s,x->p)
typedef enum Signal_e {
S_ENTRY,
S_EXIT,
S_USER_0,
S_MAX
} Signal;
typedef struct Event_s {
Signal s;
int p;
} Event;
typedef enum State_e {
INIT = 0,
WAIT,
RUN
} State;
// ステート関数の型
typedef int (*STATEFUNC)(const Event *e);
// ステートに対応する関数
int StateFuncInit(const Event *e) {
PRINT(call Init);
DUMP(e);
return 0;
}
int StateFuncWait(const Event *e) {
PRINT(call Wait);
DUMP(e);
return 0;
}
int StateFuncRun(const Event *e) {
PRINT(call Run);
DUMP(e);
return 1;
}
// ステート変数。生で触ってはいけない。
State state = INIT;
// ステートファンクタのテーブル
STATEFUNC StateTable[] = { StateFuncInit,
StateFuncWait,
StateFuncRun };
// ステートを変えるエイリアス
inline void TRANS(State next) {
Event e = {S_EXIT, 0};
StateTable[state](&e);
e.s = S_ENTRY;
StateTable[next](&e);
state = next;
}
// イベントを配送するエイリアス
inline void PROCESS(const Event *e) {
StateTable[state](e);
}
// TestCode
int main() {
Event e = {S_USER_0, 0};
PRINT(イベント通知(Initに配送される));
PROCESS(&e);
PRINT(WAITに遷移する);
TRANS(WAIT);
PRINT(イベント通知(Waitに配送される));
PROCESS(&e);
PRINT(RUNに遷移する);
TRANS(RUN);
PRINT(イベント通知(Runに配送される));
PROCESS(&e);
return 0;
}
実行結果
イベント通知(Initに配送される)
call Init
signal=2 param=0
WAITに遷移する
call Init
signal=1 param=0
call Wait
signal=0 param=0
イベント通知(Waitに配送される)
call Wait
signal=2 param=0
RUNに遷移する
call Wait
signal=1 param=0
call Run
signal=0 param=0
イベント通知(Runに配送される)
call Run
signal=2 param=0
ポイントはタイトルにもありますがファンクタを使っているところ。
状態をindexとする管理テーブルを定義することで、イベントの配送を1行で実現できます。
明らかにマルチスレッドには対応してません。
また、Entry,Exit で TRANS を呼ぶことは想定してません。
C++を使えばもっとカコヨク書けそうですが、それはまたどこかで。