//
//	clipfsm.cs
//
//	有限状態機械
//
//	CLiP - Common Library for P/ECE
//	Copyright (C) 2017 Naoyuki Sawa
//
//	* Sat Mar 04 23:52:36 JST 2017 Naoyuki Sawa
//	- 1st リリース。
//	* Sun Mar 05 23:35:48 JST 2017 Naoyuki Sawa
//	- TBL_FsmFuncの設定例を追記しました。
//	  コメントの追記だけであり、コードは変更していません。
//	* Mon Mar 06 23:24:18 JST 2017 Naoyuki Sawa
//	- リフレクションを利用して、TBL_FsmFuncを自動的に設定するように変更しました。
//	  もう、アプリケーションが明示的にFsmFunc.CreateTable()を呼び出す必要は有りません。
//	- 以下の条件を前提としています。
//	  ・関数テーブルは、dFsmMapC.exeによって作成されたFsmFuncクラスによって定義されている事。
//	  ・FsmFuncクラスは、アプリケーションのexeファイルのアセンブリに含んでビルドされている事。
//	  一般的には上記の条件から外れる事は無いと思いますが、もし例外的にその必要が生じた場合は、関数テーブルを明示的に設定するための'set'アクセサの追加を検討して下さい。
//	- 上記の変更を行うために、今回は、TBL_FsmFuncをプロパティに変更して、getアクセサを設けると言う方法を取りました。
//	  別の方法として、静的コンストラクタや静的フィールド初期化子を使って設定する方法も検討したのですが、その方法は良くないと思いました。
//	  なぜならば、TBL_FsmFuncはlibclipクラスの静的フィールドであり、もし静的コンストラクタや静的フィールド初期化子を使ってTBL_FsmFuncを設定すると、アプリケーションが有限状態機械の機能を使用しているかどうかに関係無く、常に設定してしまうからです。
//	  その結果、有限状態機械の機能を使用していないアプリケーション(=おそらくFsmFuncクラスの定義は含んでいないだろう)である場合も、FsmFunc.CreateTable()を呼び出そうとしてしまい、エラーが発生します。
//	  従って、静的コンストラクタや静的フィールド初期化子を使って設定する方法は良くありません。
//	  今回採用した、TBL_FsmFuncプロパティのgetアクセサを使う方法ならば、有限状態機械の機能がTBL_FsmFuncプロパティにアクセスしない限り(=アプリケーションが有限状態機械の機能を使用しない限り)、FsmFunc.CreateTable()は呼び出されず、問題は生じません。
//	* Fri Mar 17 23:53:28 JST 2017 Naoyuki Sawa
//	- 2017/03/06に工夫した方法(上述)がUnityエディタ上では上手く動作しなかったので、Unityでは別の方法を使うように条件コンパイルで分けました。
//	  詳細は、TBL_FsmFuncのgetアクセサ内のコメントを参照して下さい。
//	* Thu Mar 30 23:36:41 JST 2017 Naoyuki Sawa
//	- Unity環境では(仕方無く)TBL_FsmFuncのsetアクセサを追加しました。
//	  当ライブラリをUnityのマネージドプラグインとしてdll化(/clip/libclip.net/keep/Library/)した事に伴い、上記の2017/03/17に対応した方法ではTBL_FsmFuncを自動的に設定出来なくなりました。
//	  仕方が無いので、Unity環境では、アプリケーションが起動時に明示的に「TBL_FsmFunc=FsmFunc.CreateTable();」を実行して、TBL_FsmFuncを設定する事にしました。
//	  そのために、Unity環境では(仕方無く)TBL_FsmFuncのsetアクセサを追加しました。
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace org.piece_me {
	public static partial class libclip {
		//*****************************************************************************
		//	グローバル変数
		//*****************************************************************************
		//アプリケーション定義の関数配列
	//{{2017/03/06変更:リフレクションを利用して、TBL_FsmFuncを自動的に設定するように変更しました。もう、アプリケーションが明示的にFsmFunc.CreateTable()を呼び出す必要は有りません。
	//	// - C言語版では、ライブラリのビルド時点では未定義であり、アプリケーション側で定義していました。
	//	//   C#では、ライブラリのビルド時点で未定義に出来ないので、配列ポインタとして定義しておく事にしました。
	//	//   アプリケーションの起動時に、この変数を設定して下さい。
	//	// - □設定例
	//	//   │static void Main() {
	//	//   │  libclip.TBL_FsmFunc = FsmFunc.CreateTable();	//詳細は、/clip/tool/dFsmMapC/winapp.hの'Sun Mar 05 23:35:48 JST 2017 Naoyuki Sawa'のコメントを参照して下さい。
	//	//   │  …
	//	//   │}
	//	public static object[]		TBL_FsmFunc;
	//↓2017/03/06変更:リフレクションを利用して、TBL_FsmFuncを自動的に設定するように変更しました。もう、アプリケーションが明示的にFsmFunc.CreateTable()を呼び出す必要は有りません。
		// * Mon Mar 06 23:24:18 JST 2017 Naoyuki Sawa
		// - リフレクションを利用して、TBL_FsmFuncを自動的に設定するように変更しました。
		//   もう、アプリケーションが明示的にFsmFunc.CreateTable()を呼び出す必要は有りません。
		// - 以下の条件を前提としています。
		//   ・関数テーブルは、dFsmMapC.exeによって作成されたFsmFuncクラスによって定義されている事。
		//   ・FsmFuncクラスは、アプリケーションのexeファイルのアセンブリに含んでビルドされている事。
		//   一般的には上記の条件から外れる事は無いと思いますが、もし例外的にその必要が生じた場合は、関数テーブルを明示的に設定するための'set'アクセサの追加を検討して下さい。
		// - 上記の変更を行うために、今回は、TBL_FsmFuncをプロパティに変更して、getアクセサを設けると言う方法を取りました。
		//   別の方法として、静的コンストラクタや静的フィールド初期化子を使って設定する方法も検討したのですが、その方法は良くないと思いました。
		//   なぜならば、TBL_FsmFuncはlibclipクラスの静的フィールドであり、もし静的コンストラクタや静的フィールド初期化子を使ってTBL_FsmFuncを設定すると、アプリケーションが有限状態機械の機能を使用しているかどうかに関係無く、常に設定してしまうからです。
		//   その結果、有限状態機械の機能を使用していないアプリケーション(=おそらくFsmFuncクラスの定義は含んでいないだろう)である場合も、FsmFunc.CreateTable()を呼び出そうとしてしまい、エラーが発生します。
		//   従って、静的コンストラクタや静的フィールド初期化子を使って設定する方法は良くありません。
		//   今回採用した、TBL_FsmFuncプロパティのgetアクセサを使う方法ならば、有限状態機械の機能がTBL_FsmFuncプロパティにアクセスしない限り(=アプリケーションが有限状態機械の機能を使用しない限り)、FsmFunc.CreateTable()は呼び出されず、問題は生じません。
		private static object[]		_TBL_FsmFunc;		//作成した関数テーブルを保持する隠しフィールド(null=未作成)。'TBL_FsmFunc.get'内以外から参照してはいけません。
	//{{2017/03/30変更:Unity環境では(仕方無く)TBL_FsmFuncのsetアクセサを追加しました。
	//	private static object[]		TBL_FsmFunc {		//アプリケーションが明示的にTBL_FsmFuncを設定する必要が無くなったので、public⇒privateに変更しました。
	//		get {
	//			//まだ関数テーブルが作成されていなければ…
	//			if(_TBL_FsmFunc == null) {
	//	//{{2017/03/17追加:2017/03/06に工夫した方法(上述)がUnityエディタ上では上手く動作しなかったので、Unityでは別の方法を使うように条件コンパイルで分けました。
	//	#if     !UNITY_5_3_OR_NEWER
	//	//}}2017/03/17追加:2017/03/06に工夫した方法(上述)がUnityエディタ上では上手く動作しなかったので、Unityでは別の方法を使うように条件コンパイルで分けました。
	//				//アプリケーションのexeファイルのアセンブリから、グローバル名前空間のFsmFuncクラスを取得する。
	//				Assembly assembly = Assembly.GetEntryAssembly();	//GetExecutingAssembly()ではなくGetEntryAssembly()を使う事に注意せよ。GetExecutingAssembly()では現在実行中のコードを含むアセンブリ(=このライブラリのdll)が取得されてしまい、アプリケーションのexeファイルのアセンブリを取得するという目的に一致しない。
	//				if(assembly == null) { throw new ApplicationException(); }	//【Unity】Unityエディタ上で実行すると、Assembly.GetEntryAssembly()がnullを返すようだ。Unityアプリ実機上ではどうなるか未確認だが、UnityエディタとUnityアプリ実機で処理を変えるのは避けたいので、Unityではどちらもまとめて別処理とする事にした。
	//				Type tFsmFunc = assembly.GetType("FsmFunc");
	//				if(tFsmFunc == null) { throw new ApplicationException(); }
	//	//{{2017/03/17追加:2017/03/06に工夫した方法(上述)がUnityエディタ上では上手く動作しなかったので、Unityでは別の方法を使うように条件コンパイルで分けました。
	//	#else //!UNITY_5_3_OR_NEWER
	//				//現在実行中のアセンブリから、グローバル名前空間のFsmFuncクラスを取得する。
	//				Type tFsmFunc = Type.GetType("FsmFunc");	//Unityでは上述の問題が生じたため、Assembly.GetEntryAssembly()が使えなかった。仕方無く、ライブラリを単独のdllとせずにアプリケーションに含めてビルドする前提で(FsmFuncが現在のアセンブリに含まれている前提で)、Type.GetType()を使ってFsmFuncを取得する事にした。
	//				if(tFsmFunc == null) { throw new ApplicationException(); }
	//	#endif//!UNITY_5_3_OR_NEWER
	//	//}}2017/03/17追加:2017/03/06に工夫した方法(上述)がUnityエディタ上では上手く動作しなかったので、Unityでは別の方法を使うように条件コンパイルで分けました。
	//				//FsmFunc.CreateTable()を呼び出して関数テーブルを作成し、隠しフィールド_TBL_FsmFuncに格納する。
	//				_TBL_FsmFunc = (object[])tFsmFunc.InvokeMember("CreateTable", BindingFlags.InvokeMethod, null, null, null);
	//				if(_TBL_FsmFunc == null) { throw new ApplicationException(); }
	//			}
	//			//関数テーブルを返す。
	//			return _TBL_FsmFunc;
	//		}
	//	}
	//↓2017/03/30変更:Unity環境では(仕方無く)TBL_FsmFuncのsetアクセサを追加しました。
		#if     !UNITY_5_3_OR_NEWER
		//□本当はこの↓処理で汎用的に動作するはずなのですが…
		private static object[]		TBL_FsmFunc {		//アプリケーションが明示的にTBL_FsmFuncを設定する必要が無くなったので、public⇒privateに変更しました。
			get {
				//まだ関数テーブルが作成されていなければ…
				if(_TBL_FsmFunc == null) {
					//アプリケーションのexeファイルのアセンブリから、グローバル名前空間のFsmFuncクラスを取得する。
					Assembly assembly = Assembly.GetEntryAssembly();	//GetExecutingAssembly()ではなくGetEntryAssembly()を使う事に注意せよ。GetExecutingAssembly()では現在実行中のコードを含むアセンブリ(=このライブラリのdll)が取得されてしまい、アプリケーションのexeファイルのアセンブリを取得するという目的に一致しない。
					if(assembly == null) { throw new ApplicationException(); }	//【Unity】Unityエディタ上で実行すると、Assembly.GetEntryAssembly()がnullを返すようだ。Unityアプリ実機上ではどうなるか未確認だが、UnityエディタとUnityアプリ実機で処理を変えるのは避けたいので、Unityではどちらもまとめて別処理とする事にした。
					Type tFsmFunc = assembly.GetType("FsmFunc");
					if(tFsmFunc == null) { throw new ApplicationException(); }
					//FsmFunc.CreateTable()を呼び出して関数テーブルを作成し、隠しフィールド_TBL_FsmFuncに格納する。
					_TBL_FsmFunc = (object[])tFsmFunc.InvokeMember("CreateTable", BindingFlags.InvokeMethod, null, null, null);
					if(_TBL_FsmFunc == null) { throw new ApplicationException(); }
				}
				//関数テーブルを返す。
				return _TBL_FsmFunc;
			}
		}
		#else //!UNITY_5_3_OR_NEWER
		public static object[]		TBL_FsmFunc {		//Unityでは(仕方無く)アプリケーションが明示的にTBL_FsmFuncをsetする必要が生じたため、TBL_FsmFuncとsetアクセサはpublicにしました。getアクセサはアプリケーションから参照する必要は無いので、これまで通りprivateにしました。
			//□Unity環境で当ライブラリをdll化した場合は、UnityのAssembly.GetEntryAssembly()が正しく動かないようなので、こう↓せざるを得ませんでした。
			private get {
				//関数テーブルが設定されている事を確認する。
				// - Unityアプリケーションは、起動時に明示的に「TBL_FsmFunc=FsmFunc.CreateTable();」を実行して、TBL_FsmFuncを設定して下さい。
				if(_TBL_FsmFunc == null) { throw new ApplicationException(); }	//アプリケーションが上記の処理を忘れると、ここでエラー停止します。
				//関数テーブルを返す。
				return _TBL_FsmFunc;
			}
			set {
				_TBL_FsmFunc = value;
			}
		}
		#endif//!UNITY_5_3_OR_NEWER
	//}}2017/03/30変更:Unity環境では(仕方無く)TBL_FsmFuncのsetアクセサを追加しました。
	//}}2017/03/06変更:リフレクションを利用して、TBL_FsmFuncを自動的に設定するように変更しました。もう、アプリケーションが明示的にFsmFunc.CreateTable()を呼び出す必要は有りません。
		//*****************************************************************************
		//	定数
		//*****************************************************************************
		//状態,又は,状態マップのレジストリキーに属する、キーの名前
		public const int Fsm_Trans_default	= 0;		//デフォルト遷移
		//状態マップのレジストリキーに属する、キーの名前
		public const int Fsm_Init		= 0xFFFF;	//初期化情報キー	必須
		//初期化情報キーに属する、値の名前
		public const int Fsm_InitAct		= 1;		//初期化Act関数		任意
		public const int Fsm_InitState		= 2;		//初期状態		必須
												//	┌-初期化--┐ ┌-通常遷移┐ ┌-Push遷移┐ ┌-Pop遷移-┐
		//状態,又は,状態マップのレジストリキーに属する、値の名前			//	遷移元 遷移先 遷移元 遷移先 遷移元 遷移先 遷移元 遷移先
		public const int Fsm_Entry		= 1;		//Entry関数			//	  －     ○     －     ○     －     ○     －     －  
		public const int Fsm_Exit		= 2;		//Exit関数			//	  －     －     ○     －     －     －     ○     －  
		public const int Fsm_Suspend		= 3;		//Suspend関数			//	  －     －     －     －     ○     －     －     －  
		public const int Fsm_Resume		= 4;		//Resume関数			//	  －     －     －     －     －     －     －     ○  
												//	└-初期化--┘ └-通常遷移┘ └-Push遷移┘ └-Pop遷移-┘
		//遷移のレジストリキーに属する、値の名前
		//public const int Fsm_Guard		= (3N+0);	//Guard関数
		//public const int Fsm_Act		= (3N+1);	//Act関数
		//public const int Fsm_State		= (3N+2);	//遷移先状態
		//*****************************************************************************
		//	構造体
		//*****************************************************************************
		public const int FsmObj_StackSize	= 3;		//状態スタックの要素数(調整可)
		//-----------------------------------------------------------------------------
		//FSMオブジェクト
		public class ST_FsmObj {
			public VoidPtr		pMapKey;					//状態マップのレジストリキー
			public ushort		iStack;						//状態スタックのスタックポインタ
			public ushort[]		TBL_Stack	= new ushort[FsmObj_StackSize];	//状態スタック				TBL_Stack[iStack]が、現在の状態に相当します。
		}
		//*****************************************************************************
		//	ローカル関数
		//*****************************************************************************
		//アプリケーション定義の関数が有れば呼び出す。
		//アプリケーション定義の関数が無ければ、trueを返す。
		// - C言語版では、当関数の戻り値はint型でしたが、C#版では実際の意味に合わせてbool型に変更しました。
		//   当関数がfalseを返すのは、関数が有り、それがGuard関数で、Guard関数がfalseを返した場合のみです。
		private static bool FsmObj_CallFunc(ST_FsmObj pFsmObj, int iFunc, int iTrans, object pParam) {
			//指定された関数番号が(-1)ならば、何もせずにtrueを返す。
			// - FsmObj_ApplyTrans()が、Guard関数が無ければ遷移を実行するために、この戻り値は必須です。
			if(iFunc == -1) { return true; }
			//指定された関数番号が(0)以下,又は,関数配列の要素数以上ならば、エラー停止する。
			// - アプリケーション定義の関数配列の先頭に、関数配列の要素数が格納されている。
			if((iFunc <= 0) || (iFunc >= (int)TBL_FsmFunc[0])) { throw new ApplicationException(); }
			//アプリケーション定義の関数を呼び出す。
			// - C言語版では、余分な引数や戻り値は無視されるので、実際の関数の種類を意識する必要は有りませんでした。
			//   C#では、デリゲートの型を厳密に一致しないと呼び出せないので、C言語版よりも処理が増えてしまいました。
			//   どの処理からFsmObj_CallFunc()が呼び出されたかによって関数の種類は特定出来るので、当関数の引数として指定する方法も有るのですが、
			//   なるべくC言語版と同じ実装にしておきたかったので、当関数にて実際の関数の種類を判定する事にしました。
			//   関数の種類については、/clip/tool/dFsmMapC/winapp.hのST_Func.opのコメントを参照して下さい。
			object pFunc = TBL_FsmFunc[iFunc];
			//1. Entry/Exit/Suspend/Resume
			var pFunc1 = pFunc as Action<ST_FsmObj/*pFsmObj*/,int/*iAltState*/>;
			if(pFunc1 != null) { pFunc1.Invoke(pFsmObj, iTrans); return true/*ダミー*/; }
			//2. Guard
			var pFunc2 = pFunc as Func<ST_FsmObj/*pFsmObj*/,int/*iTrans*/,object/*pParam*/,bool>;
			if(pFunc2 != null) { return pFunc2.Invoke(pFsmObj, iTrans, pParam); }
			//3. Act
			var pFunc3 = pFunc as Action<ST_FsmObj/*pFsmObj*/,int/*iTrans*/,object/*pParam*/>;
			if(pFunc3 != null) { pFunc3.Invoke(pFsmObj, iTrans, pParam); return true/*ダミー*/; }
			//関数の型は、上記のいずれかであるはずです。
			throw new ApplicationException();	//バグ
		}
		//-----------------------------------------------------------------------------
		//状態マップのレジストリキーを取得する。
		private static VoidPtr FsmObj_GetMapKey(ST_FsmObj pFsmObj) {
			return pFsmObj.pMapKey;
		}
		//-----------------------------------------------------------------------------
		//現在の状態のレジストリキーを取得する。
		private static VoidPtr FsmObj_GetStateKey(ST_FsmObj pFsmObj) {
			return REG_open_key_l(
				FsmObj_GetMapKey(pFsmObj),		//状態マップのレジストリキー
				FsmObj_GetState(pFsmObj) | 0x8000);	//現在の状態の状態番号		状態のキーの名前は最上位ビットをセットする。状態マップのキーの直下に状態と遷移のキーが並存するので、状態と遷移のキーの名前が重複する事を避けるためです。
		}
		//-----------------------------------------------------------------------------
		//現在の状態の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関数を呼び出すためです。	┘
		private 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関数番号の値の名前
			if(iFunc == -1) {				//現在の状態にEntry/Exit/Suspend/Resume関数が無ければ…
				iFunc = REG_get_value_l(
					FsmObj_GetMapKey(pFsmObj),	//状態マップのレジストリキー
					iFuncName);			//Entry/Exit/Suspend/Resume関数番号の値の名前
			}
			FsmObj_CallFunc(pFsmObj, iFunc, iAltState, null/*ダミー*/);	//Entry/Exit/Suspend/Resume関数が無ければ、ダミー処理となる。
		}
		//-----------------------------------------------------------------------------
		//状態が指定されていたら、状態に属する指定された遷移のレジストリキーを取得する。
		//状態が指定されなければ、マップに属する指定された遷移のレジストリキーを取得する。
		private static VoidPtr FsmObj_GetTransKey(ST_FsmObj pFsmObj, int iState, int iTrans) {
			//C#版の当関数の実装は、C言語版とは違っています。
			// - C言語版では、REG_open_key_l()の引数namesが(-1)で終端する事を利用して、状態が指定された場合も状態が指定されていない場合も共通の処理にして、効率の良い実装にしていたのですが、
			//   C#版では、REG_open_key_l()の引数namesが(-1)で終端しないので、C言語版の方法は使えず、状態が指定された場合と状態が指定されていない場合を、別々の処理にせざるを得ませんでした。
			//状態が指定されていたら…
			if(iState != -1) {
				return REG_open_key_l(
					FsmObj_GetMapKey(pFsmObj),	//状態マップのレジストリキー
					iState | 0x8000,		//(指定された状態の状態番号|0x8000)	状態のキーの名前は最上位ビットをセットする。状態マップのキーの直下に状態と遷移のキーが並存するので、状態と遷移のキーの名前が重複する事を避けるためです。
					iTrans);			//(指定された遷移の遷移番号)
			//状態が指定されなければ…
			} else {
				return REG_open_key_l(
					FsmObj_GetMapKey(pFsmObj),	//状態マップのレジストリキー
					iTrans);			//(指定された遷移の遷移番号)
			}
		}
		//*****************************************************************************
		//	基本的な関数
		//*****************************************************************************
		public static void FsmObj_Init(ST_FsmObj pFsmObj, VoidPtr pFsmKey, int iMap, object pParam) {
			int iFnAct, iState;
			//構造体をクリアする。
		//不要	pFsmObj.pMapKey   = null;				//状態マップのレジストリキー
			pFsmObj.iStack    = 0;					//状態スタックのスタックポインタ
		//不要	pFsmObj.TBL_Stack = new ushort[FsmObj_StackSize];	//状態スタック
			//状態マップのレジストリキーを格納する。
			pFsmObj.pMapKey = REG_open_key_l(pFsmKey, iMap);
			//状態マップのレジストリキーが無ければ、エラー停止する。
			if(!pFsmObj.pMapKey) { throw new ApplicationException(); }
			//初期化Act関数と、初期状態の状態番号を取得する。
			iFnAct = REG_get_value_l(FsmObj_GetMapKey(pFsmObj), Fsm_Init, Fsm_InitAct);	//任意
			iState = REG_get_value_l(FsmObj_GetMapKey(pFsmObj), Fsm_Init, Fsm_InitState);	//必須
			//初期状態の状態番号が無ければ、エラー停止する。
			if(iState == -1) { throw new ApplicationException(); }
			//初期状態の状態番号を格納する。
			pFsmObj.TBL_Stack[0/*=(pFsmObj.iStack)*/] = (ushort)iState;
			//初期状態のレジストリキーが存在しなければ、エラー停止する。
			if(!FsmObj_GetStateKey(pFsmObj)) { throw new ApplicationException(); }
			//初期化Act関数が有れば呼び出す。
			if(iFnAct != -1) { FsmObj_CallFunc(pFsmObj, iFnAct, -1/*=iTrans*/, pParam); }
			//Entry関数が有れば呼び出す。
			FsmObj_CallStateFunc(pFsmObj, Fsm_Entry, -1/*=iPrevState*/);
		}
		//-----------------------------------------------------------------------------
		//現在の状態の状態番号を取得する。
		public static int FsmObj_GetState(ST_FsmObj pFsmObj) {
			if((uint)pFsmObj.iStack >= FsmObj_StackSize) { throw new ApplicationException(); }
			return pFsmObj.TBL_Stack[pFsmObj.iStack];
		}
		//-----------------------------------------------------------------------------
		//状態をセットする。
		public static void FsmObj_SetState(ST_FsmObj pFsmObj, int iState) {
			//変更前の状態を取得しておく。
			int iPrevState = FsmObj_GetState(pFsmObj);
			//Exit関数が有れば呼び出す。
			FsmObj_CallStateFunc(pFsmObj, Fsm_Exit, iState/*=iNextState*/);
			//指定された状態番号をセットする。
			if((uint)pFsmObj.iStack >= FsmObj_StackSize) { throw new ApplicationException(); }
			pFsmObj.TBL_Stack[pFsmObj.iStack] = (ushort)iState;
			//指定された状態のレジストリキーが存在しなければ、エラー停止する。
			if(!FsmObj_GetStateKey(pFsmObj)) { throw new ApplicationException(); }
			//Entry関数が有れば呼び出す。
			FsmObj_CallStateFunc(pFsmObj, Fsm_Entry, iPrevState);
		}
		//-----------------------------------------------------------------------------
		//状態をプッシュする。
		public static void FsmObj_PushState(ST_FsmObj pFsmObj, int iState) {
			//変更前の状態を取得しておく。
			int iPrevState = FsmObj_GetState(pFsmObj);
			//Suspend関数が有れば呼び出す。
			FsmObj_CallStateFunc(pFsmObj, Fsm_Suspend, iState/*=iNextState*/);
			//指定された状態番号をプッシュする。
			if((uint)(++pFsmObj.iStack) >= FsmObj_StackSize) { throw new ApplicationException(); }
			pFsmObj.TBL_Stack[pFsmObj.iStack] = (ushort)iState;
			//指定された状態のレジストリキーが存在しなければ、エラー停止する。
			if(!FsmObj_GetStateKey(pFsmObj)) { throw new ApplicationException(); }
			//Entry関数が有れば呼び出す。
			FsmObj_CallStateFunc(pFsmObj, Fsm_Entry, iPrevState);
		}
		//-----------------------------------------------------------------------------
		//状態をポップする。
		public static void FsmObj_PopState(ST_FsmObj pFsmObj) {
			//変更前の状態を取得しておく。
			int iPrevState = FsmObj_GetState(pFsmObj);
			//Exit関数が有れば呼び出す。
			if((uint)(pFsmObj.iStack - 1) >= FsmObj_StackSize) { throw new ApplicationException(); }
			FsmObj_CallStateFunc(pFsmObj, Fsm_Exit, pFsmObj.TBL_Stack[pFsmObj.iStack - 1]/*=iNextState*/);
			//状態番号をポップする。
			// - 上の処理で、(pFsmObj.iStack)が1以上である事を検査済みですが、ここでもう一度、検査する事にしました。
			//   万一、Exit関数が不正な処理を行って、(pFsmObj.iStack)が壊れるようなバグを、ここで検出するためです。
			if((uint)(--pFsmObj.iStack) >= FsmObj_StackSize) { throw new ApplicationException(); }
			//Resume関数が有れば呼び出す。
			FsmObj_CallStateFunc(pFsmObj, Fsm_Resume, iPrevState);
		}
		//-----------------------------------------------------------------------------
		//遷移を適用する。
		public const int Fsm_Trans_noreq = 0x8000; //iTrans引数にこのフラグをOrすると、実行可能な遷移が無くてもエラー停止しない。
		public static void FsmObj_ApplyTrans(ST_FsmObj pFsmObj, int iTrans, object pParam) {
			int iTrDef, bNoReq;
			//iTrans引数は、遷移番号(0x0000～0x7FFF),又は,(遷移番号|Fsm_Trans_noreq)(0x8000～0xFFFF)であるはず。
			//0xFFFFを超える値が指定された場合は、呼び出し側のバグである可能性が高いので、ここでエラー停止する。
			if((uint)iTrans > ushort.MaxValue) { throw new ApplicationException(); }
			//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(;;) {
					//遷移のキーを取得する。
					VoidPtr pKey = FsmObj_GetTransKey(pFsmObj, iStMap, iTrDef);
					int i = 0;
					for(;;) {
						//指定された遷移のGuard関数,Act関数,遷移先状態番号を取得する。
						int iFnGrd = REG_get_value_l(pKey, i++);	//i=(3N+0)
						int iFnAct = REG_get_value_l(pKey, i++);	//i=(3N+1)
						int iStNex = REG_get_value_l(pKey, i++);	//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) != 0) { 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 == 0) { throw new ApplicationException(); }
		}
		//*****************************************************************************
		//	ユーティリティ関数
		//*****************************************************************************
		public static void FsmObj_Init_l(ST_FsmObj pFsmObj, VoidPtr pFsmKey, int iMap, params object[] args) {
			FsmObj_Init_v(pFsmObj, pFsmKey, iMap, args);
		}
		public static void FsmObj_Init_v(ST_FsmObj pFsmObj, VoidPtr pFsmKey, int iMap, object[] args) {
			FsmObj_Init(pFsmObj, pFsmKey, iMap, args);
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		public static void FsmObj_ApplyTrans_l(ST_FsmObj pFsmObj, int iTrans, params object[] args) {
			FsmObj_ApplyTrans_v(pFsmObj, iTrans, args);
		}
		public static void FsmObj_ApplyTrans_v(ST_FsmObj pFsmObj, int iTrans, object[] args) {
			FsmObj_ApplyTrans(pFsmObj, iTrans, args);
		}
		//-----------------------------------------------------------------------------
		//pFsmObjの状態がiStateに一致するならば、0以外の値を返す。
		//pFsmObjの状態がiStateに一致しなければ、0を返す。
		public static bool FsmObj_IsState(ST_FsmObj pFsmObj, int iState) {
			return FsmObj_IsState_l(pFsmObj, iState);
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//pFsmObjの状態が可変引数のいずれかに一致するならば、0以外の値を返す。
		//pFsmObjの状態が可変引数のいずれにも一致しなければ、0を返す。
		public static bool FsmObj_IsState_l(ST_FsmObj pFsmObj, params int[] aState/*[[[[state,]state],…,]state]*/) {		//C言語版と違ってC#版では終端の(-1)は不要です。
			return FsmObj_IsState_v(pFsmObj, aState);
		}
		public static bool FsmObj_IsState_v(ST_FsmObj pFsmObj, IEnumerable<int> aState/*[[[[state,]state],…,]state]*/) {	//C言語版と違ってC#版では終端の(-1)は不要です。
			return FsmObj_StateIn_v(FsmObj_GetState(pFsmObj), aState);
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//iStateが可変引数のいずれかに一致するならば、0以外の値を返す。
		//iStateが可変引数のいずれにも一致しなければ、0を返す。
		// - 以下の関数は、数値iStateを可変引数の中から検索するだけの処理であり、当モジュールの機能に依存していません。
		//   有限状態機械の状態番号に限らず、任意の種類の数値に対しても、汎用的に使用する事が出来ます。
		public static bool FsmObj_StateIn_l(int iState, params int[] aState/*[[[[state,]state],…,]state]*/) {		//C言語版と違ってC#版では終端の(-1)は不要です。
			return FsmObj_StateIn_v(iState, aState);
		}
		public static bool FsmObj_StateIn_v(int iState, IEnumerable<int> aState/*[[[[state,]state],…,]state]*/) {	//C言語版と違ってC#版では終端の(-1)は不要です。
			//C#版の当関数の実装は、C言語版とは違っています。
			// - C言語版では、FsmObj_StateIn_v()の引数aStateが(-1)で終端する事を利用して、0,又は,0以外を返す、効率の良い実装にしていたのですが、
			//   C#版では、FsmObj_StateIn_v()の引数aStateが(-1)で終端しないので、C言語版の方法は使えず、.NETの機能を使って単純な実装にしました。
			return aState.Contains(iState);
		}
	}
}
