/*
 *	clipfsm.h
 *
 *	有限状態機械
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2016 Naoyuki Sawa
 *
 *	* Sat Jun 20 18:44:16 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sun Jun 21 22:22:12 JST 2015 Naoyuki Sawa
 *	- 当モジュールで使用するデータを生成するための、有限状態機械マップコンパイラを作りました。
 *	  /clip/tool/dFsmMapC/を参照してください。
 *	* Mon Jun 29 21:48:11 JST 2015 Naoyuki Sawa
 *	- 遷移の名前が同じで、ガード条件が異なる、遷移セットを定義出来るようにしました。
 *	  優先順位は、「SMC - The State Machine Compiler」(http://smc.sourceforge.net/)を参考にさせて頂きました。
 *	    1-1-1. 現在の状態の、指定された遷移の、ガードが有る遷移で、ガード条件が成立した遷移	複数有る場合は順不同。現在の実装ではガード名の順としているが、その挙動に依存してはいけない。
 *	    1-1-2. 現在の状態の、指定された遷移の、ガードが無い遷移
 *	    1-2-1. 状態マップの、指定された遷移の、ガードが有る遷移で、ガード条件が成立した遷移	複数有る場合は順不同。現在の実装ではガード名の順としているが、その挙動に依存してはいけない。
 *	    1-2-2. 状態マップの、指定された遷移の、ガードが無い遷移
 *	    2-1-1. 現在の状態の、デフォルト遷移の、ガードが有る遷移で、ガード条件が成立した遷移	複数有る場合は順不同。現在の実装ではガード名の順としているが、その挙動に依存してはいけない。
 *	    2-1-2. 現在の状態の、デフォルト遷移の、ガードが無い遷移
 *	    2-2-1. 状態マップの、デフォルト遷移の、ガードが有る遷移で、ガード条件が成立した遷移	複数有る場合は順不同。現在の実装ではガード名の順としているが、その挙動に依存してはいけない。
 *	    2-2-2. 状態マップの、デフォルト遷移の、ガードが無い遷移
 *	  上記の順に検索して、実行できる遷移がなかった場合は、エラー停止します。
 *	  詳細は、/clip/tool/dFsmMapC/Sample/Sample.txt の例を参照してください。
 *	* Wed Jul 01 21:16:16 JST 2015 Naoyuki Sawa
 *	- FsmObj_CancelTrans()を追加しました。
 *	  FsmObj_ApplyTrans()から呼び出された、Act関数の中から、遷移をキャンセル出来るようになりました。
 *	- 原則として遷移をキャンセルするのは、Guard関数で判断すべきです。
 *	  しかし実際には、Act関数まで処理が進んでから、遷移をキャンセルしたいケースも有ります。(エラー検出や例外処理など)
 *	  そのようなケースに対応するために、FsmObj_CancelTrans()を追加する事にしました。
 *	- Act関数がFsmObj_CancelTrans()を使って遷移をキャンセルする事と、Guard関数が遷移を受け入れない事は、等価ではありません。
 *	  Act関数がFsmObj_CancelTrans()を使って遷移をキャンセルした場合、上記の優先順位に従った次の遷移を検索する動作は行われない事に注意してください。
 *	- FsmObj_CancelTrans()を呼び出せるのは、Act関数の中からだけです。
 *	  Enter関数,Leave関数,Guard関数の中から、呼び出してはいけません。
 *	- Act関数の中で、遷移をキャンセルすると共に、明示的に状態をセット(又は,プッシュ,ポップ)を行う事も可能です。
 *	  <例>
 *	  │Map1
 *	  │  State1
 *	  │    Trans1 State2 {}
 *	  │  end
 *	  │  State2
 *	  │    ...
 *	  │  end
 *	  │  StateE
 *	  │    ...
 *	  │  end
 *	  │end
 *	  ├────────────────────────────────────────
 *	  │void FsmFunc_Map1_State1_act(ST_FsmObj* pFsmObj, int iTrans, void* pParam) {
 *	  │  if(例外判定) {
 *	  │    FsmObj_SetState(pFsmObj, FsmState_StateE);┬明示的に状態'StateE'をセットし、
 *	  │    FsmObj_CancelTrans(); ──────────┘State2への遷移をキャンセルする。
 *	  │} }
 *	* Fri Jul 03 21:00:32 JST 2015 Naoyuki Sawa
 *	- FsmObj_CancelTrans()を削除しました。
 *	  今後は構造化例外処理(SEH)を使用してください。
 *	  構造化例外処理(SEH)を使用する方が、より柔軟に制御出来ます。
 *	  <例1>
 *	  │//Act関数側で、遷移キャンセル後のエラー状態も設定する例
 *	  │void Sample1() {
 *	  │  SEH_try {
 *	  │    FsmObj_ApplyTrans(&stFsmObj, FsmTrans_Trans1, NULL);
 *	  │  } SEH_catch(&stFsmObj) {
 *	  │  } SEH_end
 *	  │}
 *	  │void FsmFunc_Map1_State1_act(ST_FsmObj* pFsmObj, int iTrans, void* pParam) {
 *	  │  if(例外判定) {
 *	  │    FsmObj_SetState(pFsmObj, FsmState_StateE);┬明示的に状態'StateE'をセットし、
 *	  │    SEH_throw(pFsmObj); ───────────┘State2への遷移をキャンセルする。
 *	  │  }
 *	  │}
 *	  <例2>
 *	  │//呼び出し側で、遷移キャンセル後のエラー状態を設定する例
 *	  │void Sample2() {
 *	  │  SEH_try {
 *	  │    FsmObj_ApplyTrans(&stFsmObj, FsmTrans_Trans1, NULL);
 *	  │  } SEH_catch(&stFsmObj) {───────────┬遷移'Trans1'がキャンセルされたら…
 *	  │    FsmObj_SetState(&stFsmObj, FsmState_StateE);┘明示的に状態'StateE'をセットする。
 *	  │  } SEH_end
 *	  │}
 *	  │void FsmFunc_Map1_State1_act(ST_FsmObj* pFsmObj, int iTrans, void* pParam) {
 *	  │  if(例外判定) {
 *	  │    SEH_throw(pFsmObj); ─────────────遷移をキャンセルする。
 *	  │  }
 *	  │}
 *	* Wed Nov 18 21:37:53 JST 2015 Naoyuki Sawa
 *	- 'extern "C" {'～'}'で囲みました。.cppを含むWin32プロジェクトからもインクルード出来るようにするためです。
 *	* Wed Jan 06 21:02:12 JST 2016 Naoyuki Sawa
 *	- 2015/07/03にFsmObj_CancelTrans()を削除した時に、コメント化して残していたコードを、完全に削除して整理しました。
 *	  もし見返す必要が生じた場合は、前回以前のアーカイブを参照して下さい。
 *	* Wed Jan 06 22:02:30 JST 2016 Naoyuki Sawa
 *	- Enter関数に、'変更前の状態'を示す引数を追加しました。
 *	  ただし特例ケースとして、FsmObj_Init()経由でEnter関数を呼び出す場合のみ、'変更前の状態'が存在しないので(-1)を渡します。	←←■■重要■■
 *	- Leave関数に、'変更後の状態'を示す引数を追加しました。
 *	  上述のEnter関数の特例ケースと違って、Leave関数の場合は'変更後の状態'は常に存在しているので、(-1)を渡す事は有りません。
 *	- これらの引数を追加した理由は、Enter関数の中で移行元の状態によって処理を少し変えたり、Leave関数の中で移行先の状態によって処理を少し変えたいケースが多かったからです。
 *	  厳密には、そのような処理は有限状態機械の設計としては望ましくないのですが、実用性を優先して引数を追加する事にしました。
 *	* Wed Jan 20 21:28:09 JST 2016 Naoyuki Sawa
 *	- Suspend関数/Resume関数を追加しました。
 *	  ・これまで、Push遷移時は、変更前の状態に対して通知が行われませんでした。
 *	    意味上はそれでも正しいのですが、実用上使い辛いという問題が有りました。
 *	    そこで、Push遷移時は変更前の状態に対して、Suspend関数を呼び出すようにしました。
 *	  ・これまで、Pop遷移時は、変更後の状態に対して通知が行われませんでした。
 *	    意味上はそれでも正しいのですが、実用上使い辛いという問題が有りました。
 *	    そこで、Pop遷移時は変更後の状態に対して、Resume関数を呼び出すようにしました。
 *	- 既存のEnter関数/Leave関数の名前を変更し、Start関数/Stop関数としました。
 *	  FSMの用語としては、Enter/Leave(又は,より正確にはEntry/Exit)の方が正しいのですが、Suspend/Resumeとの対比上、Enter/LeaveよりStart/Stopの方が判り易いと思ったからです。
 *	  この変更点については、名前の変更だけであり、動作には変更は有りません。
 *	* Wed Feb 17 21:39:25 JST 2016 Naoyuki Sawa
 *	- 既存のStart関数/Stop関数の名前を変更し、Entry関数/Exit関数としました。
 *	  2016/01/20のコメントの理由でStart関数/Stop関数としていたのですが、やはり一般的なFSMの用語に合わせる方が良いと考え直して、Entry関数/Exit関数に変更する事にしました。
 *	* Fri Feb 26 21:51:12 JST 2016 Naoyuki Sawa
 *	- FsmObj_ApplyTrans_l(),FsmObj_ApplyTrans_a(),FsmObj_ApplyTrans_v()を追加しました。
 *	  用途は、clipfsm.c内の、上記の関数の実装箇所の、コメントを参照して下さい。
 *	* Sat Feb 27 21:21:24 JST 2016 Naoyuki Sawa
 *	- 有限状態機械の初期状態を指定する方法を変更しました。
 *	- 変更前は、アプリケーションプログラムが、FsmObj_Init()の引数として、初期状態を明示的に指定していました。
 *	  変更後は、状態マップ毎のレジストリに初期状態の状態番号を格納しておき、FsmObj_Init()がその値を読み出して初期状態を取得するようにしました。
 *	- このように変更した理由は、アプリケーションプログラムの組み易さのためです。
 *	  普通は、状態マップ毎の初期状態は一種類であり、FsmObj_Init()の引数として初期状態を明示的に指定する事のメリットは、ほとんどありませんでした。
 *	  それよりも、アプリケーションが各状態マップの正しい初期状態を把握しておかなければいけない事と、もし間違えると発見しづらいバグが生じるデメリットがありました。
 *	  変更後は、アプリケーションが各状態マップの正しい初期状態を把握しておく必要は無くなり、FsmObj_Init()の引数も一つ減って、上記の問題が解決しました。
 *	- 変更後のデメリットとしては、異なる初期状態で有限状態機械を初期化する事は出来なくなった事です。
 *	  とは言え、前述のとおり、そのような事をする必要は滅多にないので、普通は問題になりません。
 *	  もしそのような事をする必要が有ったとしても、ダミーの初期状態を一つ用意して、ダミーの初期状態に初期化した後に、明示的にFsmObj_SetState()を使えば同じ事が実現出来ます。
 *	  従って、実用上、変更後のデメリットはほぼ無いと言えます。
 *	- 今回の変更に伴い、/clip/tool/dFsmMapC/ も変更しました。
 *	  有限状態機械スクリプト上での、初期状態の指定方法については、上記のツールのソースの、同日の変更点を参照して下さい。
 *	* Thu Mar 03 21:33:11 JST 2016 Naoyuki Sawa
 *	- FsmObj_ApplyTrans()関数に、Fsm_Trans_noreqフラグを追加しました。
 *	  FsmObj_ApplyTrans()関数を呼び出している他の関数(FsmObj_ApplyTrans_l(),ActObj_ApplyTransToTree_l()等)でも使えます。
 *	- これまでは、iTrans引数で指定した遷移番号に対する実行可能な遷移が無かった場合(存在する遷移が全てガード条件で否定された場合も含む)は、エラー停止していました。
 *	  原則的にはそれで良いのですが、ActObj_ApplyTransToTree()等で、オブジェクトツリー全体に遷移を適用する場合、一部のオブジェクトが遷移を実装していないケースが生じました。
 *	  それらのオブジェクトに、'何もしないダミー遷移'を実装してエラーを回避するよりも、FsmObj_ApplyTrans()関数側で対応する方が自然だと考えて、今回のフラグ追加を行いました。
 *	  尚、今回の行った変更は、UnityのGameObject.SendMessage関数の、DontRequireReceiverフラグ指定を見て、思い付きました。
 *	- Fsm_Trans_noreqフラグを乱用しないように、注意して下さい。
 *	  プログラムの意図として'存在するべき'遷移が存在しなかった場合はエラー停止する方が妥当であり、そのような遷移に対してもFsm_Trans_noreqフラグを指定すると、エラーが発生せずにバグを見落とす恐れが有るからです。
 *	  Fsm_Trans_noreqフラグを指定する対象としては、前述のような、オブジェクトツリー全体に対して遷移を適用するケース,かつ,
 *	  ほとんどのオブジェクトが実装している汎用的な遷移(Draw,Exec等)を一部のオブジェクトだけが意図的に実装していないことが事前に判っていてエラーを避ける場合や、	…下記の使用例④に相当
 *	  ほとんどのオブジェクトが実装していない特殊な遷移を実装している一部のオブジェクトだけに対してメッセージを送りたい場合等に、限定して使用するのが安全です。	…下記の使用例⑤に相当
 *	- <使用例>
 *	  ① FsmObj_ApplyTrans(pFsmObj, FsmTrans_Draw, NULL);					//pFsmObjに実行可能なDraw遷移,又は,default遷移が無ければ、エラー停止する。				…これまでと同じ
 *	  ② FsmObj_ApplyTrans(pFsmObj, FsmTrans_Draw|Fsm_Trans_noreq, NULL);			//pFsmObjに実行可能なDraw遷移,又は,default遷移が無ければ、何もしない。					…今回追加した動作
 *	  ③ ActObj_ApplyTransToTree(pActObj, FsmTrans_Draw, NULL);				//オブジェクトツリーの中に、FsmTrans_Drawを実装していないオブジェクトが有れば、エラー停止する。		…これまでと同じ
 *	  ④ ActObj_ApplyTransToTree(pActObj, FsmTrans_Draw|Fsm_Trans_noreq, NULL);		//オブジェクトツリーの中に、FsmTrans_Drawを実装していないオブジェクトが有っても、エラー停止しない。	…今回追加した動作
 *	  ⑤ ActObj_ApplyTransToTree(pActObj, FsmTrans_SpecialTrans|Fsm_Trans_noreq, NULL);	//オブジェクトツリーの中で、FsmTrans_SpecialTransを実装しているオブジェクトだけに、メッセージを送る。	…今回追加した動作
 *	* Sat Apr 02 23:34:06 JST 2016 Naoyuki Sawa
 *	- ARRAY_SIZE()マクロの変更に伴い、VC++6.0でintとunsignedの比較に対する警告が厳密に出るようになったので、警告が出ないように明示的なキャストを追加しました。
 *	  動作自体はこれまでと変わりません。
 *	  詳細はclipmisc.hの同日のコメントを参照して下さい。
 *	* Fri Jul 08 21:19:03 JST 2016 Naoyuki Sawa
 *	- ST_FsmObj_Arg型の構造体を追加しました。
 *	  FsmObj_ApplyTrans_l(),FsmObj_ApplyTrans_a(),FsmObj_ApplyTrans_v()を使って有限状態機械のAct関数を呼び出した場合は、Act関数へのpParam引数はこの型の構造体へのポインタとなります。
 *	  これまでは、argvだけをAct関数へのpParam引数としていたので、Act関数側でargcを参照する事が出来ませんでした。
 *	  今後は、argcもST_FsmObj_Arg型の構造体に格納してAct関数へのpParam引数となるので、Act関数側でargcを参照する事が出来るようになりました。
 *	- 上記はあくまでも、FsmObj_ApplyTrans_l(),FsmObj_ApplyTrans_a(),FsmObj_ApplyTrans_v()を使って有限状態機械のAct関数を呼び出した場合です。
 *	  直接FsmObj_ApplyTrans()を使って有限状態機械のAct関数を呼び出した場合は、Act関数へのpParam引数はこれまで通りアプリケーション定義の任意のデータとなる事に注意して下さい。
 *	- 上記と同じ変更を、clipact.cモジュールの、ActObj_ApplyTrans_l(),ActObj_ApplyTransToChildren_l(),ActObj_ApplyTransToTree_l()等に対しても適用しました。
 *	  詳細は、clipact.cモジュールの、同日のコメントを参照して下さい。
 *	- 余談ですが、これまで、上記の関数のアセンブラ版の実装は、スタック操作のバグが有った事に気付きました。
 *	  今回の変更に合わせて、スタック操作のバグも修正しました。
 *	* Sat Jul 09 21:38:42 JST 2016 Naoyuki Sawa
 *	- FsmObj_CallStateFunc()関数の仮引数iStateの名前を、iAltStateに変更しました。
 *	  FsmObj_CallStateFunc()関数の仮引数は、現在の状態を示しているのではなく、遷移先状態,又は,遷移元状態を示している事を強調するためです。
 *	  clipfsm.c内のローカル関数の仮引数名の変更のみであり、動作は変わりません。
 *	- 上記と同様に、dFsmMapC.exeが出力する関数プロトタイプの仮引数名も変更しました。
 *	  詳細は、/clip/tool/dFsmMapC/winapp.hの同日のコメントを参照して下さい。
 *	* Mon Aug 29 21:46:36 JST 2016 Naoyuki Sawa
 *	- FsmObj_IsState(),FsmObj_IsState_l(),FsmObj_IsState_a(),FsmObj_IsState_v(),FsmObj_StateIn_l(),FsmObj_StateIn_a(),FsmObj_StateIn_v()を追加しました。
 *	* Tue Aug 30 21:54:03 JST 2016 Naoyuki Sawa
 *	- FsmObj_StateIn_v()のアルゴリズムを少し変更して高速化しました。
 *	- FsmObj_IsState(),FsmObj_IsState_l(),FsmObj_IsState_a(),FsmObj_IsState_v(),FsmObj_StateIn_l(),FsmObj_StateIn_a(),FsmObj_StateIn_v()をアセンブラ化しました。
 */
#ifndef __CLIP_FSM_H__
#define __CLIP_FSM_H__
#ifdef  __cplusplus
extern "C" {
#endif//__cplusplus
/****************************************************************************
 *	
 ****************************************************************************/
//状態,又は,状態マップのレジストリキーに属する、キーの名前
#define Fsm_Trans_default	0		//デフォルト遷移
//状態マップのレジストリキーに属する、値の名前
#define Fsm_State_init		0		//初期状態
										//	┌-初期化--┐ ┌-通常遷移┐ ┌-Push遷移┐ ┌-Pop遷移-┐
//状態,又は,状態マップのレジストリキーに属する、値の名前			//	遷移元 遷移先 遷移元 遷移先 遷移元 遷移先 遷移元 遷移先
#define Fsm_Entry		1		//Entry関数			//	  －     ○     －     ○     －     ○     －     －  
#define Fsm_Exit		2		//Exit関数			//	  －     －     ○     －     －     －     ○     －  
#define Fsm_Suspend		3		//Suspend関数			//	  －     －     －     －     ○     －     －     －  
#define Fsm_Resume		4		//Resume関数			//	  －     －     －     －     －     －     －     ○  
										//	└-初期化--┘ └-通常遷移┘ └-Push遷移┘ └-Pop遷移-┘
//遷移のレジストリキーに属する、値の名前
//#define Fsm_Guard		(3N+0)		//Guard関数
//#define Fsm_Act		(3N+1)		//Act関数
//#define Fsm_State		(3N+2)		//遷移先状態
/*--------------------------------------------------------------------------*/
#define FsmObj_StackSize	3		//状態スタックの要素数(調整可)
//FSMオブジェクト
typedef struct _ST_FsmObj {
	const void*	pMapKey;						//+ 0,4 状態マップのレジストリキー
	uint16_t	iStack;							//+ 4,2 状態スタックのスタックポインタ
	uint16_t	TBL_Stack[FsmObj_StackSize];				//+ 6,6 状態スタック				TBL_Stack[iStack]が、現在の状態に相当します。
} ST_FsmObj;									//=12
/*--------------------------------------------------------------------------*/
//アプリケーション定義の関数配列。
//先頭要素に、関数配列の要素数を格納する事。
extern const void* const TBL_FsmFunc[];
//配列要素の関数は以下のいずれかの形式です。					//状態変更	FsmObj_GetState()が返す状態	備考
typedef void FsmFunc_entry(  ST_FsmObj* pFsmObj, int iPrevState);		//不可		変更後の状態			現在の状態を変更した後に、この関数を呼びます。
typedef void FsmFunc_exit(   ST_FsmObj* pFsmObj, int iNextState);		//不可		変更前の状態			この関数を呼んでから、現在の状態を変更します。
typedef void FsmFunc_suspend(ST_FsmObj* pFsmObj, int iNextState);		//不可		変更前の状態			この関数を呼んでから、現在の状態を変更します。
typedef void FsmFunc_resume( ST_FsmObj* pFsmObj, int iPrevState);		//不可		変更後の状態			現在の状態を変更した後に、この関数を呼びます。
typedef int  FsmFunc_guard(  ST_FsmObj* pFsmObj, int iTrans, void* pParam);	//不可		変更前の状態			Guard関数が(0)を返したら、この遷移を行わない。
typedef void FsmFunc_act(    ST_FsmObj* pFsmObj, int iTrans, void* pParam);	//可		変更前の状態			遷移後状態へ移行する前に、この関数を呼びます。
/****************************************************************************
 *	基本的な関数
 ****************************************************************************/
void FsmObj_Init(      ST_FsmObj* pFsmObj, const void* pFsmKey, int iMap);
int  FsmObj_GetState(  ST_FsmObj* pFsmObj);
void FsmObj_SetState(  ST_FsmObj* pFsmObj, int iState);
void FsmObj_PushState( ST_FsmObj* pFsmObj, int iState);
void FsmObj_PopState(  ST_FsmObj* pFsmObj);
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#define Fsm_Trans_noreq 0x8000 //iTrans引数にこのフラグをOrすると、実行可能な遷移が無くてもエラー停止しない。
void FsmObj_ApplyTrans(ST_FsmObj* pFsmObj, int iTrans, void* pParam);				//─pParamがそのまま、Act関数へのpParam引数となります。
/****************************************************************************
 *	ユーティリティ関数
 ****************************************************************************/
//FsmObj_ApplyTrans_l(),FsmObj_ApplyTrans_a(),FsmObj_ApplyTrans_v()を使って有限状態機械のAct関数を呼び出した場合は、Act関数へのpParam引数はこの型の構造体へのポインタとなります。
//ただし、直接FsmObj_ApplyTrans()を使って有限状態機械のAct関数を呼び出した場合は、Act関数へのpParam引数はこれまで通りアプリケーション定義の任意のデータとなる事に注意して下さい。
typedef struct _ST_FsmObj_Arg {
	int		argc;			//+0,4
	void**		argv/*[argc]*/;		//+4,4
} ST_FsmObj_Arg;				//=8
/*--------------------------------------------------------------------------*/
void FsmObj_ApplyTrans_l(ST_FsmObj* pFsmObj, int iTrans, int argc, ...);			//┐
void FsmObj_ApplyTrans_a(ST_FsmObj* pFsmObj, int iTrans, int argc, va_list ap);			//├可変引数の情報をST_FsmObj_Arg型の構造体に格納して、Act関数へのpParam引数となります。
void FsmObj_ApplyTrans_v(ST_FsmObj* pFsmObj, int iTrans, int argc, void* argv[/*argc*/]);	//┘
/*--------------------------------------------------------------------------*/
int FsmObj_IsState(  ST_FsmObj* pFsmObj, int iState);
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int FsmObj_IsState_l(ST_FsmObj* pFsmObj, ...);
int FsmObj_IsState_a(ST_FsmObj* pFsmObj, va_list ap);
int FsmObj_IsState_v(ST_FsmObj* pFsmObj, const int* aState/*[[[[state,]state],…,]state,]-1*/);
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int FsmObj_StateIn_l(int iState, ...);
int FsmObj_StateIn_a(int iState, va_list ap);
int FsmObj_StateIn_v(int iState, const int* aState/*[[[[state,]state],…,]state,]-1*/);
#ifdef  __cplusplus
}//extern "C"
#endif//__cplusplus
#endif//__CLIP_FSM_H__
