//
//	cliptimm.cs
//
//	タイマ管理
//
//	CLiP - Common Library for P/ECE
//	Copyright (C) 2017 Naoyuki Sawa
//
//	* Wed Mar 08 22:12:10 JST 2017 Naoyuki Sawa
//	- 1st リリース。
//	* Mon Aug 28 23:20:51 JST 2017 Naoyuki Sawa
//	- 'TimMgrCh_1exec'を追加しました。
//	  2017/08/28にcliptimm.cに対して行った変更を、C#版にも適用したものです。
//	* Mon Aug 28 23:46:08 JST 2017 Naoyuki Sawa
//	- TimMgr_AddFunc_New(),TimMgrFunc_New(),TimMgrFunc_Init()を追加しました。
//	  2017/08/28にcliptimm.cに対して行った変更を、C#版にも適用したものです。
//
using System;
namespace org.piece_me {
	public static partial class libclip {
		//*****************************************************************************
		//	定数、構造体
		//*****************************************************************************
		//タイマ管理チャネル番号
		public const int TimMgrCh_1f		= 0;		//1f	　同期タイマ
		public const int TimMgrCh_10ms		= 1;		//10ms	　同期タイマ
		public const int TimMgrCh_100ms		= 2;		//100ms	　同期タイマ
		public const int TimMgrCh_1s		= 3;		//1s	　同期タイマ
		public const int TimMgrCh_1ms		= 4;		//1ms	非同期タイマ
//{{2017/08/28変更:'TimMgrCh_1exec'を追加しました。
//		public const int TimMgrCh_SIZE		= 5;
//↓2017/08/28変更:'TimMgrCh_1exec'を追加しました。
		public const int TimMgrCh_1exec		= 5;		//1exec	　同期タイマ
		public const int TimMgrCh_SIZE		= 6;
//}}2017/08/28変更:'TimMgrCh_1exec'を追加しました。
		//-----------------------------------------------------------------------------
		//関数ノード構造体
		public class ST_TimMgrFunc {
			public Action<object/*arg*/>	fn;		//関数			null不可	アプリケーションがTimMgr_AddFunc()を呼び出す前に設定せよ。
			public object			arg;		//関数引数		null可		アプリケーションがTimMgr_AddFunc()を呼び出す前に設定せよ。
			public ushort			uItv;		//関数インターバル	   0不可	アプリケーションがTimMgr_AddFunc()を呼び出す前に設定せよ。
			public ushort			uCnt;		//関数カウンタ		   0~uCnt	アプリケーションがTimMgr_AddFunc()を呼び出す時に不定値で構わない。
			public ST_TimMgrFunc		pNext;		//次の関数ノード	null=終端	アプリケーションがTimMgr_AddFunc()を呼び出す時に不定値で構わない。
		}
		//-----------------------------------------------------------------------------
		//タイマ管理チャネル構造体
		public class ST_TimMgrCh {
//{{2017/08/28コメント変更:'TimMgrCh_1exec'を追加しました。
//			public          ushort		uSub;		//減算値		1000,(uAdd*10),(uAdd*100),(uAdd*1000),0/*終端*/
//↓2017/08/28コメント変更:'TimMgrCh_1exec'を追加しました。
			public          ushort		uSub;		//減算値		1000,(uAdd*10),(uAdd*100),(uAdd*1000),0/*終端*/,0/*1exec*/
//}}2017/08/28コメント変更:'TimMgrCh_1exec'を追加しました。
			public volatile ushort		uErr;		//チャネル誤差項	1msチャネルでは使用しない。
			public volatile int		uCnt;		//チャネルカウンタ
			public ST_TimMgrFunc		pNext;		//関数チェイン
		}
		//-----------------------------------------------------------------------------
		//タイマ管理構造体
		public class ST_TimMgr {
			public byte				uAdd;							//加算値	1~65	フレームレートと同じ。
			public ST_TimMgrCh[/*TimMgrCh_SIZE*/]	TBL_TimMgrCh	= new ST_TimMgrCh[TimMgrCh_SIZE];	//タイマ管理チャネル
		}
		//*****************************************************************************
		//	ローカル関数
		//*****************************************************************************
		private static void TimMgr_Invoke(ST_TimMgrCh pCh) {
			ST_TimMgrFunc pFunc;
			//チャネルカウンタを'1'進める。
			pCh.uCnt++;
			//関数チェインの終端に到達するまで…
			for(pFunc = pCh.pNext; pFunc != null; pFunc = pFunc.pNext) {
				//関数カウンタを'1'増やし、関数インターバルに到達したら…
				if(++pFunc.uCnt >= pFunc.uItv) {
					//関数カウンタをリセットする。
					pFunc.uCnt = 0;
					//関数を呼び出す。
					pFunc.fn.Invoke(pFunc.arg);
				}
			}
		}
		//*****************************************************************************
		//	アプリケーション用関数
		//*****************************************************************************
		public static void TimMgr_Init(ST_TimMgr pTimMgr, int uFps) {
			//フレームレートが1~65の範囲外ならば、エラー停止する。
			if((uFps < 1) || (uFps > (ushort.MaxValue/1000))) { throw new ApplicationException(); }
			//構造体をクリアする。
		//不要	pTimMgr.uAdd = 0;
		//不要	pTimMgr.TBL_TimMgrCh = new ST_TimMgrCh[TimMgrCh_SIZE];
			for(int iCh = 0; iCh < TimMgrCh_SIZE; iCh++) {
				pTimMgr.TBL_TimMgrCh[iCh] = new ST_TimMgrCh();
			}
			//加算値を設定する。
			pTimMgr.uAdd = (byte)uFps;
			//減算値を設定する。
			pTimMgr.TBL_TimMgrCh[TimMgrCh_1f   ].uSub =                 1000;
			pTimMgr.TBL_TimMgrCh[TimMgrCh_10ms ].uSub = (ushort)(uFps *= 10);
			pTimMgr.TBL_TimMgrCh[TimMgrCh_100ms].uSub = (ushort)(uFps *= 10);
			pTimMgr.TBL_TimMgrCh[TimMgrCh_1s   ].uSub = (ushort)(uFps *= 10);
		//不要	pTimMgr.TBL_TimMgrCh[TimMgrCh_1ms  ].uSub = 0;
//{{2017/08/28コメント追加:'TimMgrCh_1exec'を追加しました。
		//不要	pTimMgr.TBL_TimMgrCh[TimMgrCh_1exec].uSub = 0;
//}}2017/08/28コメント追加:'TimMgrCh_1exec'を追加しました。
		}
		//-----------------------------------------------------------------------------
		public static void TimMgr_AddFunc(ST_TimMgr pTimMgr, int iCh, ST_TimMgrFunc pFunc) {
			//チャネル番号が範囲外,又は,関数ノードの関数と関数インターバルが未設定ならば、エラー停止する。
			if(((uint)iCh >= TimMgrCh_SIZE) || (pFunc.fn == null) || (pFunc.uItv == 0)) { throw new ApplicationException(); }
		    //{{この順序で処理すれば、既にタイマが開始していても、割り込み禁止が不要である。
			//追加する関数ノードの関数カウンタをクリアし、ポインタを終端にする。
			pFunc.uCnt  = 0;
			pFunc.pNext = null;
			//指定されたチャネルの、関数チェインの終端に、関数ノードを追加する。
			ST_TimMgrCh pCh = pTimMgr.TBL_TimMgrCh[iCh];
			ST_TimMgrFunc ppNext = pCh.pNext;
			if(ppNext == null) {
				pCh.pNext = pFunc;
			} else {
				for(;;) {
					if(ppNext == pFunc) { throw new ApplicationException(); }	//同じ関数ノードを二回登録しようとしたら、エラー停止する。
					if(ppNext.pNext == null) { break; }
					ppNext = ppNext.pNext;
				}
				ppNext.pNext = pFunc;
			}
		    //}}この順序で処理すれば、既にタイマが開始していても、割り込み禁止が不要である。
		}
		/*--------------------------------------------------------------------------*/
//{{2017/08/28追加:TimMgr_AddFunc_New(),TimMgrFunc_New(),TimMgrFunc_Init()を追加しました。
		//ユーティリティ関数：ST_TimMgrFunc構造体を動的に確保して、タイマ管理に登録する。
		public static ST_TimMgrFunc TimMgr_AddFunc_New(ST_TimMgr pTimMgr, int iCh, Action<object/*arg*/> fn, object arg, int uItv) {
			ST_TimMgrFunc pFunc = TimMgrFunc_New(fn, arg, uItv);
			TimMgr_AddFunc(pTimMgr, iCh, pFunc);
			return pFunc;	//動的に確保して登録したST_TimMgrFunc構造体を返します。もしタイマ管理の終了後に動的に確保したST_TimMgrFunc構造体を開放したい場合はこのポインタを覚えておいて下さい。通常は無視して構いません(通常はタイマ管理の終了とアプリの終了が同時でありOSのメモリ回収に任せられるから)。
		}
		/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
		//ユーティリティ関数：ST_TimMgrFunc構造体を動的に確保する。
		public static ST_TimMgrFunc TimMgrFunc_New(Action<object/*arg*/> fn, object arg, int uItv) {
			ST_TimMgrFunc pFunc = new ST_TimMgrFunc();
			TimMgrFunc_Init(pFunc, fn, arg, uItv);
			return pFunc;
		}
		/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
		//ユーティリティ関数：ST_TimMgrFunc構造体を静的に初期化する。
		public static void TimMgrFunc_Init(ST_TimMgrFunc pFunc, Action<object/*arg*/> fn, object arg, int uItv) {
			pFunc.fn    = fn;
			pFunc.arg   = arg;
			pFunc.uItv  = (ushort)uItv;
		//不要	pFunc.uCnt  = 0;	//┬TimMgr_AddFunc()が登録時にクリアするので、ここではクリア不要です。
		//不要	pFunc.pNext = null;	//┘
		}
//}}2017/08/28追加:TimMgr_AddFunc_New(),TimMgrFunc_New(),TimMgrFunc_Init()を追加しました。
		//-----------------------------------------------------------------------------
		public static int TimMgr_GetCnt(ST_TimMgr pTimMgr, int iCh) {
			//チャネル番号が範囲外ならば、エラー停止する。
			if((uint)iCh >= TimMgrCh_SIZE) { throw new ApplicationException(); }
			//チャネルカウンタを返す。
			return pTimMgr.TBL_TimMgrCh[iCh].uCnt;
		}
		//-----------------------------------------------------------------------------
		public static void TimMgr_Exec(ST_TimMgr pTimMgr) {
			int uErr;
			foreach(ST_TimMgrCh pCh in pTimMgr.TBL_TimMgrCh) {
				if(pCh.uSub == 0) {	//1msチャネル(pCh.uSub=0)は含めない。
					break;	//ここまで
				}
				//以下の処理で減算できる間、繰り返す。
				// - ループ中に割り込みが発生してpCh.uErrが増えても安全である。
				for(;;) {
/*ENTER_CS;*/				lock(pCh) {
						//チャネル誤差項が減算値以上ならば、減算する。
						if((uErr = pCh.uErr - pCh.uSub) >= 0) { pCh.uErr = (ushort)uErr; }
/*LEAVE_CS;*/				}
					//減算できなければ、抜ける。
					if(uErr < 0) { break; }
					//関数を呼び出す。
					TimMgr_Invoke(pCh);
				}
			}
//{{2017/08/28追加:'TimMgrCh_1exec'を追加しました。
			//1execチャネルの関数を呼び出す。
			TimMgr_Invoke(pTimMgr.TBL_TimMgrCh[TimMgrCh_1exec]);
//}}2017/08/28追加:'TimMgrCh_1exec'を追加しました。
		}
		//-----------------------------------------------------------------------------
		// * Wed Mar 08 22:12:10 JST 2017 Naoyuki Sawa
		// - C言語版では、TimMgr_Intr()は割り込み内から割り込み禁止の状態で呼び出される前提で、排他制御を行っていませんでした。
		//   C#版では、スレッド等から(暗黙の割り込み禁止無しに)呼び出される事を想定して、明示的な排他制御を行うようにしました。
		// - C言語版では、システム全体のクリティカルセクション(clipmisc.cの__cs__)を設けて、割り込み禁止と同じ事としていました。
		//   C#版では、システム全体のクリティカルセクションを設けずに、TimMgr_Exec()とTimMgr_Intr()でチャネル誤差項を変更する時だけ、そのチャネル構造体をロックするようにしました。
		//   チャネル誤差項以外は、複数のスレッド等からの変更が競合する事は無いと思うので、これで大丈夫だと思います。(※未検証)
		public static void TimMgr_Intr(ST_TimMgr pTimMgr) {
			int uErr;
			//各チャネルについて…
			foreach(ST_TimMgrCh pCh in pTimMgr.TBL_TimMgrCh) {
				if(pCh.uSub == 0) {	//1msチャネル(pCh.uSub=0)は含めない。
					//1msチャネルのみ、割り込み内で実行する。
					TimMgr_Invoke(pCh);
					break;	//ここまで
				}
				//チャネル誤差項に、加算値を加える。
/*ENTER_CS;*/			lock(pCh) {
					uErr = pCh.uErr + pTimMgr.uAdd;
					if(uErr > ushort.MaxValue) { uErr = ushort.MaxValue; }	//飽和
					pCh.uErr = (ushort)uErr;
/*LEAVE_CS;*/			}
			}
//{{2017/08/28コメント追加:'TimMgrCh_1exec'を追加しました。
			//1msチャネルの後に1execチャネルも存在していますが、TimMgr_Intr()内では1execチャネルに対して何も行う必要は有りません。
			//1execチャネルは、経過時間に関係無くTimMgr_Exec()から明示的に呼び出すので、チャネル誤差項を増やす必要は無いからです。
//}}2017/08/28コメント追加:'TimMgrCh_1exec'を追加しました。
		}
	}
}
//*****************************************************************************
//	使用例
//*****************************************************************************
#if false
using System;
using static org.piece_me.libclip;
class Program {
	static ST_TimMgr stTimMgr = new ST_TimMgr();
	static int cnt3ms,cnt100ms,cnt1f,cnt5f;
	static void myFunc3ms(  object arg) { cnt3ms++;   }//─TimMgr_Intr()内で呼び出される。
	static void myFunc100ms(object arg) { cnt100ms++; }//┐
	static void myFunc1f(   object arg) { cnt1f++;    }//├TimMgr_Intr()外で呼び出される。
	static void myFunc5f(   object arg) { cnt5f++;    }//┘
	static void init() {
		TimMgr_Init(stTimMgr, 30);
	#if false
		//□静的に確保したST_TimMgrFunc構造体を登録する例。組み込み環境等で動的なメモリ確保を避けたい場合はこちら。
		ST_TimMgrFunc stFunc3ms  =new ST_TimMgrFunc(){fn=myFunc3ms  ,uItv=3};
		ST_TimMgrFunc stFunc100ms=new ST_TimMgrFunc(){fn=myFunc100ms,uItv=1};
		ST_TimMgrFunc stFunc1f   =new ST_TimMgrFunc(){fn=myFunc1f   ,uItv=1};
		ST_TimMgrFunc stFunc5f   =new ST_TimMgrFunc(){fn=myFunc5f   ,uItv=5};
		TimMgr_AddFunc(stTimMgr, TimMgrCh_1ms,   stFunc3ms  );
		TimMgr_AddFunc(stTimMgr, TimMgrCh_100ms, stFunc100ms);
		TimMgr_AddFunc(stTimMgr, TimMgrCh_1f,    stFunc1f   );
		TimMgr_AddFunc(stTimMgr, TimMgrCh_1f,    stFunc5f   );
	#else
		//□ST_TimMgrFunc構造体を動的に確保して登録する例。P/ECEやWindows等で動的なメモリ確保が容易な場合はこちら。
		TimMgr_AddFunc_New(stTimMgr,TimMgrCh_1ms  ,myFunc3ms  ,null,3);
		TimMgr_AddFunc_New(stTimMgr,TimMgrCh_100ms,myFunc100ms,null,1);
		TimMgr_AddFunc_New(stTimMgr,TimMgrCh_1f   ,myFunc1f   ,null,1);
		TimMgr_AddFunc_New(stTimMgr,TimMgrCh_1f   ,myFunc5f   ,null,5);
	#endif
	}
	static void Main() {
		init();
		int t0 = Environment.TickCount;
		while(!Console.KeyAvailable) {
			int dt;
			while((dt = Environment.TickCount - t0) == 0) { }
			t0 += dt;
			do { TimMgr_Intr(stTimMgr); } while(--dt != 0);
			TimMgr_Exec(stTimMgr);
			Console.Write("{0,6:D} ", TimMgr_GetCnt(stTimMgr, TimMgrCh_1f));
			Console.Write("{0,6:D} ", TimMgr_GetCnt(stTimMgr, TimMgrCh_10ms));
			Console.Write("{0,6:D} ", TimMgr_GetCnt(stTimMgr, TimMgrCh_100ms));
			Console.Write("{0,6:D} ", TimMgr_GetCnt(stTimMgr, TimMgrCh_1s));
			Console.Write("{0,6:D} ", TimMgr_GetCnt(stTimMgr, TimMgrCh_1ms));
			Console.Write("{0,6:D} {1,6:D} ", cnt1f, cnt5f);
			Console.Write("{0,6:D} ", cnt100ms);
			Console.Write("{0,6:D} ", cnt3ms);
			Console.WriteLine();
		}
	}
}
#endif
