/*
 *	clipfsm.c
 *
 *	有限状態機械
 *
 *	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 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()をアセンブラ化しました。
 *	* Mon Sep 05 21:24:05 JST 2016 Naoyuki Sawa
 *	- 64ビット対応時の警告抑制のために、FsmObj_CallFunc()内でポインタを(int)にキャストしていた箇所を、(intptr_t)にキャストするように修正しました。
 *	  尚、32ビットでは(int)も(intptr_t)も同じなので、既存の32ビットコードには影響有りません。
 *	* Sat Oct 01 21:11:57 JST 2016 Naoyuki Sawa
 *	- FsmObj_ApplyTrans_v()のC言語版の実装が間違っていたのを修正しました。
 *	  「ST_FsmObj_Arg arg = { argc, argv }」を作成しているのは良いのですが、argを使わずに元のargvをそのままFsmObj_ApplyTrans()に渡してしまっていました。
 *	  正しくは、&argをFsmObj_ApplyTrans()に渡すのが正しい処理でした。
 *	  これまで気付かなかった理由は、実際のアプリケーションではFsmObj_ApplyTrans_v()を使わずに、ActObj_ApplyTrans_v()を使っていたからです(そちらの実装は正しかった)。
 *	  今回気付いたきっかけは、Hewでビルドした時に「C5177 (W) Variable "arg" was declared but never referenced」という警告が出たからです。
 *	  P/ECE開発環境(GCC)やWindows(VC++6.0,VS2015)では警告が出ないので、これまで気付かなかったのでした。
 */
#include "clip.h"
/****************************************************************************
 *	ローカル関数
 ****************************************************************************/
//アプリケーション定義の関数が有れば呼び出す。
//アプリケーション定義の関数が無ければ、(-1)を返す。
static int FsmObj_CallFunc(ST_FsmObj* pFsmObj, int iFunc, int iTrans, void* pParam) {
	//指定された関数番号が(-1)ならば、何もせずに(-1)を返す。
	if(iFunc == -1) { return -1; }
	//指定された関数番号が(0)以下,又は,関数配列の要素数以上ならば、エラー停止する。
	// - アプリケーション定義の関数配列の先頭に、関数配列の要素数が格納されている。
//{{2016/09/05変更:64ビット対応時の警告抑制のために、FsmObj_CallFunc()内でポインタを(int)にキャストしていた箇所を、(intptr_t)にキャストするように修正しました。尚、32ビットでは(int)も(intptr_t)も同じなので、既存の32ビットコードには影響有りません。
//	if((iFunc <= 0) || (iFunc >= (int)TBL_FsmFunc[0])) { DIE(); }
//↓2016/09/05変更:64ビット対応時の警告抑制のために、FsmObj_CallFunc()内でポインタを(int)にキャストしていた箇所を、(intptr_t)にキャストするように修正しました。尚、32ビットでは(int)も(intptr_t)も同じなので、既存の32ビットコードには影響有りません。
	if((iFunc <= 0) || (iFunc >= (intptr_t)TBL_FsmFunc[0])) { DIE(); }
//}}2016/09/05変更:64ビット対応時の警告抑制のために、FsmObj_CallFunc()内でポインタを(int)にキャストしていた箇所を、(intptr_t)にキャストするように修正しました。尚、32ビットでは(int)も(intptr_t)も同じなので、既存の32ビットコードには影響有りません。
	//アプリケーション定義の関数を呼び出す。
	// - 常にFsmGuardFunc()形式として呼び出す。他の形式だった場合、余分な引数や戻り値は無視される。
	// - Act関数,Guard関数を呼び出す場合は、引数の意味は引数名の通りです。
	//   Entry関数,又は,Resume関数を呼び出す場合は、iTransは実際にはiPrevState，pParamは余分な引数(ダミー)です。
	//   Exit関数,又は,Suspend関数を呼び出す場合は、iTransは実際にはiNextState，pParamは余分な引数(ダミー)です。
	return ((int (*)(ST_FsmObj*, int, void*))TBL_FsmFunc[iFunc])(pFsmObj, iTrans, pParam);
}
/*--------------------------------------------------------------------------*/
//状態マップのレジストリキーを取得する。
static const void* FsmObj_GetMapKey(ST_FsmObj* pFsmObj) {
	return pFsmObj->pMapKey;
}
/*--------------------------------------------------------------------------*/
//現在の状態のレジストリキーを取得する。
static const void* FsmObj_GetStateKey(ST_FsmObj* pFsmObj) {
	return REG_open_key_l(
		FsmObj_GetMapKey(pFsmObj),		//状態マップのレジストリキー
		FsmObj_GetState(pFsmObj) | 0x8000,	//現在の状態の状態番号		状態のキーの名前は最上位ビットをセットする。状態マップのキーの直下に状態と遷移のキーが並存するので、状態と遷移のキーの名前が重複する事を避けるためです。
		-1);
}
/*--------------------------------------------------------------------------*/
//現在の状態のEntry/Exit/Suspend/Resume関数を呼び出す。
//現在の状態にEntry/Exit/Suspend/Resume関数が無ければ、状態マップのEntry/Exit/Suspend/Resume関数を呼び出す。
//状態マップにもEntry/Exit/Suspend/Resume関数が無ければ、何もしない。
// - Entry/Resume関数を呼び出す場合は、iAltStateには遷移元状態(iPrevState)を指定して下さい。遷移元状態(iPrevState)を引数として、遷移先状態のEntry/Resume関数のEntry/Resume関数を呼び出すためです。	┬clipfsm.hのFsmFunc_entry(),FsmFunc_exit(),FsmFunc_suspend(),FsmFunc_resume()のtypedefも参照して下さい。
//   Exit/Suspend関数を呼び出す場合は、iAltStateには遷移先状態(iNextState)を指定して下さい。遷移先状態(iNextState)を引数として、遷移元状態のEntry/Resume関数のExit/Suspend関数を呼び出すためです。	┘
static void FsmObj_CallStateFunc(ST_FsmObj* pFsmObj, int iFuncName, int iAltState) {
	int iFunc = REG_get_value_l(
		FsmObj_GetStateKey(pFsmObj),		//現在の状態のレジストリキー
		iFuncName,				//Entry/Exit/Suspend/Resume関数番号の値の名前
		-1);
	if(iFunc == -1) {				//現在の状態にEntry/Exit/Suspend/Resume関数が無ければ…
		iFunc = REG_get_value_l(
			FsmObj_GetMapKey(pFsmObj),	//状態マップのレジストリキー
			iFuncName,			//Entry/Exit/Suspend/Resume関数番号の値の名前
			-1);
	}
	FsmObj_CallFunc(pFsmObj, iFunc, iAltState, NULL/*ダミー*/);	//Entry/Exit/Suspend/Resume関数が無ければ、ダミー処理となる。
}
/*--------------------------------------------------------------------------*/
//状態が指定されていたら、状態に属する指定された遷移のレジストリキーを取得する。
//状態が指定されなければ、マップに属する指定された遷移のレジストリキーを取得する。
static const void* FsmObj_GetTransKey(ST_FsmObj* pFsmObj, int iState, int iTrans) {
	const void* pKey = REG_open_key_l(
		FsmObj_GetMapKey(pFsmObj),		//状態マップのレジストリキー
		iState | 0x8000,			//(指定された状態の状態番号|0x8000),又は,-1		状態のキーの名前は最上位ビットをセットする。状態マップのキーの直下に状態と遷移のキーが並存するので、状態と遷移のキーの名前が重複する事を避けるためです。
		-1);
	return REG_open_key_l(
		pKey,					//状態マップのレジストリキー,又は,指定された状態のレジストリキー
		iTrans,					//指定された遷移のキーの名前
		-1);
}
/****************************************************************************
 *	基本的な関数
 ****************************************************************************/
//FSMオブジェクトを初期化する。
void FsmObj_Init(ST_FsmObj* pFsmObj, const void* pFsmKey, int iMap) {
	int iState;
	//構造体をクリアする。
	memset(pFsmObj, 0, sizeof(ST_FsmObj));
	//状態マップのレジストリキーを格納する。
	pFsmObj->pMapKey = REG_open_key_l(pFsmKey, iMap, -1);
	//状態マップのレジストリキーが無ければ、エラー停止する。
	if(!pFsmObj->pMapKey) { DIE(); }
	//初期状態の状態番号を取得する。
	iState = REG_get_value(FsmObj_GetMapKey(pFsmObj), Fsm_State_init);
	//初期状態の状態番号が無ければ、エラー停止する。
	if(iState == -1) { DIE(); }
	//初期状態の状態番号を格納する。
	pFsmObj->TBL_Stack[0/*=(pFsmObj->iStack)*/] = iState;
	//指定された状態のレジストリキーが存在しなければ、エラー停止する。
	if(!FsmObj_GetStateKey(pFsmObj)) { DIE(); }
	//Entry関数が有れば呼び出す。
	FsmObj_CallStateFunc(pFsmObj, Fsm_Entry, -1/*=iPrevState*/);
}
/*--------------------------------------------------------------------------*/
//現在の状態の状態番号を取得する。
int FsmObj_GetState(ST_FsmObj* pFsmObj) {
	if((unsigned)pFsmObj->iStack >= FsmObj_StackSize) { DIE(); }
	return pFsmObj->TBL_Stack[pFsmObj->iStack];
}
/*--------------------------------------------------------------------------*/
//状態をセットする。
void FsmObj_SetState(ST_FsmObj* pFsmObj, int iState) {
	//変更前の状態を取得しておく。
	int iPrevState = FsmObj_GetState(pFsmObj);
	//Exit関数が有れば呼び出す。
	FsmObj_CallStateFunc(pFsmObj, Fsm_Exit, iState/*=iNextState*/);
	//指定された状態番号をセットする。
	if((unsigned)pFsmObj->iStack >= FsmObj_StackSize) { DIE(); }
	pFsmObj->TBL_Stack[pFsmObj->iStack] = iState;
	//指定された状態のレジストリキーが存在しなければ、エラー停止する。
	if(!FsmObj_GetStateKey(pFsmObj)) { DIE(); }
	//Entry関数が有れば呼び出す。
	FsmObj_CallStateFunc(pFsmObj, Fsm_Entry, iPrevState);
}
/*--------------------------------------------------------------------------*/
//状態をプッシュする。
void FsmObj_PushState(ST_FsmObj* pFsmObj, int iState) {
	//変更前の状態を取得しておく。
	int iPrevState = FsmObj_GetState(pFsmObj);
	//Suspend関数が有れば呼び出す。
	FsmObj_CallStateFunc(pFsmObj, Fsm_Suspend, iState/*=iNextState*/);
	//指定された状態番号をプッシュする。
	if((unsigned)(++pFsmObj->iStack) >= FsmObj_StackSize) { DIE(); }
	pFsmObj->TBL_Stack[pFsmObj->iStack] = iState;
	//指定された状態のレジストリキーが存在しなければ、エラー停止する。
	if(!FsmObj_GetStateKey(pFsmObj)) { DIE(); }
	//Entry関数が有れば呼び出す。
	FsmObj_CallStateFunc(pFsmObj, Fsm_Entry, iPrevState);
}
/*--------------------------------------------------------------------------*/
//状態をポップする。
void FsmObj_PopState(ST_FsmObj* pFsmObj) {
	//変更前の状態を取得しておく。
	int iPrevState = FsmObj_GetState(pFsmObj);
	//Exit関数が有れば呼び出す。
	if((unsigned)(pFsmObj->iStack - 1) >= FsmObj_StackSize) { DIE(); }
	FsmObj_CallStateFunc(pFsmObj, Fsm_Exit, pFsmObj->TBL_Stack[pFsmObj->iStack - 1]/*=iNextState*/);
	//状態番号をポップする。
	// - 上の処理で、(pFsmObj->iStack)が1以上である事を検査済みですが、ここでもう一度、検査する事にしました。
	//   万一、Exit関数が不正な処理を行って、(pFsmObj->iStack)が壊れるようなバグを、ここで検出するためです。
	if((unsigned)(--pFsmObj->iStack) >= FsmObj_StackSize) { DIE(); }
	//Resume関数が有れば呼び出す。
	FsmObj_CallStateFunc(pFsmObj, Fsm_Resume, iPrevState);
}
/*--------------------------------------------------------------------------*/
//遷移を適用する。
void FsmObj_ApplyTrans(ST_FsmObj* pFsmObj, int iTrans, void* pParam) {
	int iTrDef, bNoReq;
	//iTrans引数は、遷移番号(0x0000～0x7FFF),又は,(遷移番号|Fsm_Trans_noreq)(0x8000～0xFFFF)であるはず。
	//0xFFFFを超える値が指定された場合は、呼び出し側のバグである可能性が高いので、ここでエラー停止する。
	if((unsigned)iTrans > UINT16_MAX) { DIE(); }
	//iTrans引数から、Fsm_Trans_noreqフラグを分離する。
	bNoReq = iTrans &   Fsm_Trans_noreq;
	         iTrans &= ~Fsm_Trans_noreq;
	//指定された遷移(iTrans),デフォルト遷移(0)の順に検索する。
	iTrDef = iTrans;
	for(;;) {
		//現在の状態に属する遷移,マップに属する遷移(-1)の順に検索する。
		int iStMap = FsmObj_GetState(pFsmObj);
		for(;;) {
			//遷移のキーを取得する。
			const void* pKey = FsmObj_GetTransKey(pFsmObj, iStMap, iTrDef);
			int i = 0;
			for(;;) {
				//指定された遷移のGuard関数,Act関数,遷移先状態番号を取得する。
				int iFnGrd = REG_get_value_l(pKey, i++, -1);	//i=(3N+0)
				int iFnAct = REG_get_value_l(pKey, i++, -1);	//i=(3N+1)
				int iStNex = REG_get_value_l(pKey, i++, -1);	//i=(3N+2)
				//指定された遷移のAct関数,又は,遷移先状態番号が有れば…
				if((iFnAct != -1) || (iStNex != -1)) {
					//指定された遷移のGuard関数が(0)以外を返したら、遷移を実行する。
					// - Guard関数が無ければFsmObj_CallFunc()が(-1)を返し、遷移を実行する。
					if(FsmObj_CallFunc(pFsmObj, iFnGrd, iTrans, pParam)) {		//デフォルト遷移を使用する場合も、関数引数には'指定された遷移番号'を引き渡す。
						//指定された遷移のAct関数が有れば呼び出す。
						// - Act関数が現在の状態を変える可能性が有る。
						FsmObj_CallFunc(pFsmObj, iFnAct, iTrans, pParam);	//デフォルト遷移を使用する場合も、関数引数には'指定された遷移番号'を引き渡す。
						//遷移先状態番号が無ければ、ここまで。
						if(iStNex == -1) { return; }
						//遷移先状態番号が(0xFFFF)ならば、ポップ遷移である。
						else if(iStNex == 0xFFFF) { FsmObj_PopState(pFsmObj); return; }
						//遷移先状態番号が(0x8000～0xFFFE)ならば、下位15ビットを状態番号と見なす、プッシュ遷移である。
						else if(iStNex & 0x8000) { FsmObj_PushState(pFsmObj, iStNex & 0x7FFF); return; }
						//遷移先状態番号が(0x0000～0x7FFF)ならば、通常遷移である。
						else { FsmObj_SetState(pFsmObj, iStNex); return; }
					}
				}
				//指定された遷移のGuard関数が無ければ、終端である。
				// - Act関数,遷移先状態番号の有無に関らず、Guard関数が無ければ終端である。
				if(iFnGrd == -1) { break; }
			}
			//マップに属する遷移を検索したら、このループを抜ける。
			if(iStMap == -1/*Map*/) { break; }
			//マップに属する遷移を検索する。
			   iStMap  = -1/*Map*/;
		}
		//デフォルト遷移を検索したら、このループを抜ける。
		if(iTrDef == Fsm_Trans_default) { break; }
		//デフォルト遷移を検索する。
		   iTrDef  = Fsm_Trans_default;
	}
	//実行可能な遷移が一つも無ければ、エラー停止する。
	//ただし、iTrans引数にFsm_Trans_noreqフラグがOrされていた場合は、エラー停止しない。
	if(!bNoReq) { DIE(); }
}
/****************************************************************************
 *	ユーティリティ関数
 ****************************************************************************/
// * Fri Feb 26 21:51:12 JST 2016 Naoyuki Sawa
// - FsmObj_ApplyTrans_l(),FsmObj_ApplyTrans_a(),FsmObj_ApplyTrans_v()を追加しました。
// - FsmFunc_guard(),及び,FsmFunc_act()に、複数の引数を渡すためのユーティリティ関数です。
//   これらのユーティリティ関数を使って遷移を実行した場合、呼び出されたFsmFunc_guard(),及び,FsmFunc_act()は、pParam引数をargvと解釈して下さい。
//   │void test() { FsmObj_ApplyTrans_l(pFsmObj, FsmTrans_Foo, 2, "One", 234); }
//   │int  FsmFunc_guard(ST_FsmObj* pFsmObj, int iTrans, void* pParam) { void** argv = pParam; printf("guard: %s,%d\n", argv[0], argv[1]); } //引数の意味に応じて'int* argv'や'char** argv'等と解釈しても構いません。
//   │void FsmFunc_act(  ST_FsmObj* pFsmObj, int iTrans, void* pParam) { void** argv = pParam; printf("act:   %s,%d\n", argv[0], argv[1]); } //引数の意味に応じて'int* argv'や'char** argv'等と解釈しても構いません。
// - argcの情報は、FsmFunc_guard(),及び,FsmFunc_act()には引き渡されません。
//   これらのユーティリティ関数を使って遷移を実行する処理と、FsmFunc_guard(),及び,FsmFunc_act()は、同じアプリケーション内で定義するものであり、
//   FsmFunc_guard(),及び,FsmFunc_act()は、引数の数や引数の意味を知っていると想定出来るからです。
// * Sat Oct 01 21:11:57 JST 2016 Naoyuki Sawa
// - FsmObj_ApplyTrans_v()のC言語版の実装が間違っていたのを修正しました。
//   「ST_FsmObj_Arg arg = { argc, argv }」を作成しているのは良いのですが、argを使わずに元のargvをそのままFsmObj_ApplyTrans()に渡してしまっていました。
//   正しくは、&argをFsmObj_ApplyTrans()に渡すのが正しい処理でした。
//   これまで気付かなかった理由は、実際のアプリケーションではFsmObj_ApplyTrans_v()を使わずに、ActObj_ApplyTrans_v()を使っていたからです(そちらの実装は正しかった)。
//   今回気付いたきっかけは、Hewでビルドした時に「C5177 (W) Variable "arg" was declared but never referenced」という警告が出たからです。
//   P/ECE開発環境(GCC)やWindows(VC++6.0,VS2015)では警告が出ないので、これまで気付かなかったのでした。
#ifndef PIECE
void FsmObj_ApplyTrans_l(ST_FsmObj* pFsmObj, int iTrans, int argc, ...) {
	va_list ap;
	va_start(ap, argc);
	FsmObj_ApplyTrans_a(pFsmObj, iTrans, argc, ap);
	va_end(ap);
}
void FsmObj_ApplyTrans_a(ST_FsmObj* pFsmObj, int iTrans, int argc, va_list ap) {
	void* argv[16/*調整可*/];	//実用上、パラメータ数が16以下と考えて充分だろう。不足した場合は増やせ。
	int i;
	if((unsigned)argc > (unsigned)ARRAY_SIZE(argv)) { DIE(); }	//ここで停止した場合は、argv[]の要素数を増やせ。
	for(i = 0; i < argc; i++) { argv[i] = va_arg(ap, void*); }
	FsmObj_ApplyTrans_v(pFsmObj, iTrans, argc, argv);
}
void FsmObj_ApplyTrans_v(ST_FsmObj* pFsmObj, int iTrans, int argc, void* argv[/*argc*/]) {
	ST_FsmObj_Arg arg = { argc, argv };
//{{2016/10/01修正:FsmObj_ApplyTrans_v()のC言語版の実装が間違っていたのを修正しました。
//	FsmObj_ApplyTrans(pFsmObj, iTrans, argv);
//↓2016/10/01修正:FsmObj_ApplyTrans_v()のC言語版の実装が間違っていたのを修正しました。
	FsmObj_ApplyTrans(pFsmObj, iTrans, &arg);
//}}2016/10/01修正:FsmObj_ApplyTrans_v()のC言語版の実装が間違っていたのを修正しました。
}
#else //PIECE
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);
void FsmObj_ApplyTrans_v(ST_FsmObj* pFsmObj, int iTrans, int argc, void* argv[/*argc*/]);
asm("
		.code
		.align		1
		.global		FsmObj_ApplyTrans_l
		.global		FsmObj_ApplyTrans_a
		.global		FsmObj_ApplyTrans_v
FsmObj_ApplyTrans_l:
		;//%r12    := pFsmObj
		;//%r13    := iTrans
		;//[%sp+0] := retp
		;//[%sp+4] := argc
		;//[%sp+8] := ...
		xld.w		%r14, [%sp+4]		;//%r14 := argc
		xadd		%r15, %sp, 8		;//%r15 := ap = &...
FsmObj_ApplyTrans_a:
		;//%r12    := pFsmObj
		;//%r13    := iTrans
		;//%r14    := argc
		;//%r15    := ap
FsmObj_ApplyTrans_v:
		xsub		%sp, %sp, 8
		;//%r12    := pFsmObj
		;//%r13    := iTrans
		;//%r14    := argc
		;//%r15    := argv
		;//[%sp+0] := (arg.argc)
		;//[%sp+4] := (arg.argv)
		xld.w		[%sp+0], %r14			;//[%sp+0] := arg.argc = argc
		xld.w		[%sp+4], %r15			;//[%sp+4] := arg.argv = argv
		ld.w		%r14, %sp			;//%r14    := pParam = &arg
		xcall		FsmObj_ApplyTrans		;//FsmObj_ApplyTrans(pFsmObj, iTrans, pParam)
		xadd		%sp, %sp, 8
		ret
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
#ifndef PIECE
//pFsmObjの状態がiStateに一致するならば、0以外の値を返す。
//pFsmObjの状態がiStateに一致しなければ、0を返す。
int FsmObj_IsState(ST_FsmObj* pFsmObj, int iState) {
	return FsmObj_IsState_l(pFsmObj, iState, -1);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//pFsmObjの状態が可変引数(-1で終端)のいずれかに一致するならば、0以外の値を返す。
//pFsmObjの状態が可変引数(-1で終端)のいずれにも一致しなければ、0を返す。
int FsmObj_IsState_l(ST_FsmObj* pFsmObj, ...) {
	int retVal;
	va_list ap;
	va_start(ap, pFsmObj);
	retVal = FsmObj_IsState_a(pFsmObj, ap);
	va_end(ap);
	return retVal;
}
int FsmObj_IsState_a(ST_FsmObj* pFsmObj, va_list ap) {
	return FsmObj_StateIn_a(FsmObj_GetState(pFsmObj), ap);
}
int FsmObj_IsState_v(ST_FsmObj* pFsmObj, const int* aState/*[[[[state,]state],…,]state,]-1*/) {
	return FsmObj_StateIn_v(FsmObj_GetState(pFsmObj), aState);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//iStateが可変引数(-1で終端)のいずれかに一致するならば、0以外の値を返す。
//iStateが可変引数(-1で終端)のいずれにも一致しなければ、0を返す。
// - 以下の関数は、数値iStateを可変引数の中から検索するだけの処理であり、当モジュールの機能に依存していません。
//   有限状態機械の状態番号に限らず、任意の種類の数値に対しても、汎用的に使用する事が出来ます。
int FsmObj_StateIn_l(int iState, ...) {
	int retVal;
	va_list ap;
	va_start(ap, iState);
	retVal = FsmObj_StateIn_a(iState, ap);
	va_end(ap);
	return retVal;
}
int FsmObj_StateIn_a(int iState, va_list ap) {
	int aState[(15+1)/*調整可*/], i = 0;				//実用上、比較する状態の数が15以下(終端(-1)を含めて16以下)と考えて充分だろう。不足した場合は増やせ。
	for(;;) {
		if((aState[i] = va_arg(ap, int)) == -1) { break; }
		if(++i >= ARRAY_SIZE(aState)) { DIE(); }		//ここで停止した場合は、aState[]不足か,又は,呼出し側が終端を忘れている。おそらく後者だろう。
	}
	return FsmObj_StateIn_v(iState, aState);
}
int FsmObj_StateIn_v(int iState, const int* aState/*[[[[state,]state],…,]state,]-1*/) {
    //{{2016/08/30変更:FsmObj_StateIn_v()のアルゴリズムを少し変更して高速化しました。
    //	for(;;) {
    //		int iState2 = *aState++;				//比較する状態番号を取得する。
    //		if(iState2 ==     -1) { return 0; }			//比較する状態番号が終端ならば、一致しなかった事を示す0を返す。
    //		if(iState2 == iState) { return 1; }			//比較する状態番号と有限状態機械の状態番号が一致したら1を返す。
    //	}
    //↓2016/08/30変更:FsmObj_StateIn_v()のアルゴリズムを少し変更して高速化しました。
	int iState2;
	for(;;) {
		iState2 = *aState++;					//比較する状態番号を取得する。
		if((iState2 == -1) || (iState2 == iState)) { break; }	//比較する状態番号が終端か、有限状態機械の状態番号に一致したら、ループを抜ける。
	}
	return iState2 + 1;						//比較する状態番号が終端だったならば、戻り値は((-1)+1)=0となる。有限状態機械の状態番号に一致したら、戻り値は((-1以外)+1)=0以外となる。
    //}}2016/08/30変更:FsmObj_StateIn_v()のアルゴリズムを少し変更して高速化しました。
}
#else //PIECE
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*/);
asm("
		.code
		.align		1
		.global		FsmObj_IsState
		.global		FsmObj_IsState_l
		.global		FsmObj_IsState_a
		.global		FsmObj_IsState_v
		.global		FsmObj_StateIn_l
		.global		FsmObj_StateIn_a
		.global		FsmObj_StateIn_v
FsmObj_IsState:
		;//%r12 := pFsmObj
		;//%r13 := iState
		xsub		%sp, %sp, 12			;//
		xld.w		[%sp+0], %r12			;//[%sp+0] :=                           pFsmObj
		xld.w		[%sp+4], %r13			;//[%sp+4] :=                                    iState
		ld.w		%r9, -1				;//%r9     :=                                            -1
		xld.w		[%sp+8], %r9			;//[%sp+4] :=                                            -1
		call		FsmObj_IsState_l		;//%r10    := retVal = FsmObj_IsState_l(pFsmObj, iState, -1)
		xadd		%sp, %sp, 12			;//
		ret						;//return     retVal
		;//---------------------------------------------;//
FsmObj_IsState_l:
		;//[%sp+0] := retp
		;//[%sp+4] := pFsmObj
		;//[%sp+8] := ...
		xld.w		%r12, [%sp+4]			;//%r12 := pFsmObj
		xadd		%r13, %sp, 8			;//%r13 := ap = &...
		;//---------------------------------------------;//
FsmObj_IsState_a:
FsmObj_IsState_v:
		;//%r12 := pFsmObj
		;//%r13 := ap or aState
		pushn		%r0				;//								;//
		xcall.d		FsmObj_GetState			;//%r10 := iState = FsmObj_GetState(pFsmObj)			;//%r10 := iState = FsmObj_GetState(pFsmObj)
		ld.w		%r0, %r13			;//%r0  :=                          ap		*delay*		;//%r0  :=                          aState	*delay*
		ld.w		%r13, %r0			;//%r13 :=                          ap				;//%r13 :=                          aState
		popn		%r0				;//								;//
		jp.d		FsmObj_StateIn_v		;//return  FsmObj_StateIn_a(iState, ap)				;//return  FsmObj_StateIn_v(iState, aState)			FsmObj_StateIn_aもFsmObj_StateIn_vも同じなので共通化出来ます。
		ld.w		%r12, %r10			;//%r12 :=                  iState		*delay*		;//%r12 :=                  iState		*delay*
		;//---------------------------------------------;//
FsmObj_StateIn_l:
		;//[%sp+0] := retp
		;//[%sp+4] := iState
		;//[%sp+8] := ...
		xld.w		%r12, [%sp+4]			;//%r12 := iState
		xadd		%r13, %sp, 8			;//%r13 := ap = &...
		;//---------------------------------------------;//
FsmObj_StateIn_a:
FsmObj_StateIn_v:
		;//%r12 := iState
		;//%r13 := ap or aState
FsmObj_StateIn_v_LOOP:						;//for(;;) {
		ld.w		%r10, [%r13]+			;//  %r10 := iState2 = *aState++
		cmp		%r10, -1			;//  
		jreq		FsmObj_StateIn_v_RET		;//  if((iState2 ==     -1) ||
		cmp		%r10, %r12			;//  
		jrne		FsmObj_StateIn_v_LOOP		;//     (iState2 == iState)) { break }		!INTERLOCK!
FsmObj_StateIn_v_RET:						;//}
		ret.d						;//return  iState2 + 1
		add		%r10, 1				;//						*delay*
");
#endif//PIECE
