/*
 *	cliptimm.c
 *
 *	タイマ管理
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2017 Naoyuki Sawa
 *
 *	* Sat May 30 00:59:40 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	* Tue Aug 02 22:11:15 JST 2016 Naoyuki Sawa
 *	- ST_TimMgrFunc.fn の関数プロトタイプを変更しました。
 *	  ST_TimMgrFunc.argフィールドを追加しました。
 *	  これまでは、引数を取る関数をタイマに登録するためには、中継関数を経由する必要が有りました。
 *	  今回の変更によって、引数を取る関数も直接、タイマに登録する事が出来るようになりました。
 *	* Mon Aug 28 23:20:51 JST 2017 Naoyuki Sawa
 *	- 'TimMgrCh_1exec'を追加しました。
 *	  経過時間に関係無く、TimMgr_Exec()呼び出し一回毎に一回実行される同期タイマの、タイマ管理チャネル番号です。
 *	  'TimMgrCh_1exec'を追加する事にした理由は、以下の通りです。
 *	- 様々なモジュールの周期処理の呼び出しを、一元管理したいと思う事有りました。
 *	  アプリケーションのschedule()関数等から明示的に呼び出しても同じではあるのですが、一元管理する方が判り易いと思ったからです。
 *	  そこで、タイマ管理モジュール(当モジュール)の既存のTimMgr_Exec()関数に、その役割を担わさせる事にしました。
 *	  今回、タイマ管理モジュールの関数仕様には一切変更は無く、新たなタイマ管理チャネルとして'TimMgrCh_1exec'を追加しただけです。
 *	- ちなみに別の案として、タイマ管理チャネルも追加せずに、既存の'TimMgrCh_1f'を使用する方法も考えましたが、それは間違いです。
 *	  なぜなら、TimMgrCh_1fチャネルは経過時間に依存するので、TimMgr_Exec()呼び出し一回毎に一回実行されるとは限らないからです。
 *	  アプリケーションのメインループを1f単位で回していても、微妙な処理時間のずれによって、TimMgr_Exec()の呼び出し間隔が1f未満や1f超過になる可能性が有ります。
 *	  TimMgrCh_1fを使用していると、メインループのある回では周期処理が呼び出されなかったり、ある回では二回以上呼び出されたりして、意図と異なる動作になります。
 *	  上記の問題を避けるために、アプリケーションのメインループに必ず同期するチャネルとして、新たに'TimMgrCh_1exec'を設けました。
 *	- これまで、アプリケーションのschedule()関数等から明示的に呼び出していた周期処理は、今後は、タイマ管理モジュールの'TimMgrCh_1exec'チャネルに登録出来ます。
 *	  必須ではなく、これまで通りアプリケーションのschedule()関数等から明示的に呼び出しても構いませんが、判り易さのために、なるべく上記の方法を利用して下さい。
 *	  ■注意!!
 *	  │上記はあくまでも、アプリケーションのschedule()関数等の中で、一回だけTimMgr_Exec()を呼び出している構造のアプリケーションについてです。
 *	  │アプリケーションのschedule()関数等の中で、経過時間をポーリングしながら何度もTimMgr_Exec()を呼び出している構造のアプリケーションには、適用出来ません。
 *	  │具体的には、2017/08/28現在の/clip/test.win/cpp.cppの構造は後者であるため、現在の所、Windowsアプリケーションの雛形ではTimMgrCh_1execを使用出来ません。
 *	  │後者のような構造のアプリケーションについては、これまで通り、TimMgrCh_1fを使用して下さい。
 *	* Mon Aug 28 23:46:08 JST 2017 Naoyuki Sawa
 *	- TimMgr_AddFunc_New(),TimMgrFunc_New(),TimMgrFunc_Init()を追加しました。
 *	  元々、当モジュールは組み込み環境を想定していたので、呼び出し側にてST_TimMgrFunc構造体を静的に確保する事を想定しており、TimMgr_AddFunc()の関数仕様がやや使い辛い問題が有りました。
 *	  そこで、動的なメモリ確保が容易な環境ではもっと簡単に使えるように、上記の関数を追加しました。
 *	  具体的な使用例は、cliptimm.cの「使用例」の「□ST_TimMgrFunc構造体を動的に確保して登録する例。P/ECEやWindows等で動的なメモリ確保が容易な場合はこちら。」のコードを参照して下さい。
 */
#include "clip.h"
/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/
#ifndef PIECE
static void TimMgr_Invoke(ST_TimMgrCh* pCh);
#else //PIECE
/*static*/ void TimMgr_Invoke(ST_TimMgrCh* pCh);
#endif//PIECE
/****************************************************************************
 *	
 ****************************************************************************/
void TimMgr_Init(ST_TimMgr* pTimMgr, int uFps) {
	ST_TimMgrCh* pCh;
	//フレームレートが1~65の範囲外ならば、エラー停止する。
	if((uFps < 1) || (uFps > (USHRT_MAX/1000))) { DIE(); }
	//構造体をクリアする。
	memset(pTimMgr, 0, sizeof(ST_TimMgr));
	//加算値を設定する。
	pTimMgr->uAdd = uFps;
	//減算値を設定する。
	pCh = pTimMgr->TBL_TimMgrCh;
	pCh->uSub =         1000;   pCh++;	//1f
	pCh->uSub = (uFps *= 10);   pCh++;	//10ms
	pCh->uSub = (uFps *= 10);   pCh++;	//100ms
	pCh->uSub = (uFps *= 10); /*pCh++;*/	//1s
//{{2017/08/28コメント変更:'TimMgrCh_1exec'を追加しました。
// //不要	pCh->uSub = 0;				//1ms
//↓2017/08/28コメント変更:'TimMgrCh_1exec'を追加しました。
 //不要	pCh->uSub = 0;            /*pCh++;*/	//1ms
 //不要	pCh->uSub = 0;				//1exec
//}}2017/08/28コメント変更:'TimMgrCh_1exec'を追加しました。
}
/*--------------------------------------------------------------------------*/
void TimMgr_AddFunc(ST_TimMgr* pTimMgr, int iCh, ST_TimMgrFunc* pFunc) {
	ST_TimMgrCh* pCh;
	ST_TimMgrFunc** ppNext;
	//チャネル番号が範囲外,又は,関数ノードの関数と関数インターバルが未設定ならば、エラー停止する。
	if(((unsigned)iCh >= TimMgrCh_SIZE) || !pFunc->fn || !pFunc->uItv) { DIE(); }
    //{{この順序で処理すれば、既にタイマが開始していても、割り込み禁止が不要である。
	//指定されたチャネルの、関数チェインの終端ポインタの、変数アドレスを求める。
	pCh = &pTimMgr->TBL_TimMgrCh[iCh];
	for(ppNext = &pCh->pNext; *ppNext; ppNext = &(*ppNext)->pNext) {
		//同じ関数ノードを二回登録しようとしたら、エラー停止する。
		if(*ppNext == pFunc) { DIE(); }
	}
	//追加する関数ノードの関数カウンタをクリアし、ポインタを終端にする。
	pFunc->uCnt  = 0;
	pFunc->pNext = NULL;
	//関数ノードを追加する。
	*ppNext = pFunc;
    //}}この順序で処理すれば、既にタイマが開始していても、割り込み禁止が不要である。
}
/*--------------------------------------------------------------------------*/
//{{2017/08/28追加:TimMgr_AddFunc_New(),TimMgrFunc_New(),TimMgrFunc_Init()を追加しました。
//ユーティリティ関数：ST_TimMgrFunc構造体を動的に確保して、タイマ管理に登録する。
ST_TimMgrFunc* TimMgr_AddFunc_New(ST_TimMgr* pTimMgr, int iCh, void (*fn)(void* arg), void* 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構造体を動的に確保する。
ST_TimMgrFunc* TimMgrFunc_New(void (*fn)(void* arg), void* arg, int uItv) {
	ST_TimMgrFunc* pFunc = malloc(sizeof(ST_TimMgrFunc));
	if(!pFunc) { DIE(); }	//メモリ不足
	TimMgrFunc_Init(pFunc, fn, arg, uItv);
	return pFunc;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//ユーティリティ関数：ST_TimMgrFunc構造体を静的に初期化する。
void TimMgrFunc_Init(ST_TimMgrFunc* pFunc, void (*fn)(void* arg), void* arg, int uItv) {
	pFunc->fn    = fn;
	pFunc->arg   = arg;
	pFunc->uItv  = uItv;
//不要	pFunc->uCnt  = 0;	//┬TimMgr_AddFunc()が登録時にクリアするので、ここではクリア不要です。
//不要	pFunc->pNext = NULL;	//┘
}
//}}2017/08/28追加:TimMgr_AddFunc_New(),TimMgrFunc_New(),TimMgrFunc_Init()を追加しました。
/*--------------------------------------------------------------------------*/
int TimMgr_GetCnt(ST_TimMgr* pTimMgr, int iCh) {
	//チャネル番号が範囲外ならば、エラー停止する。
	if((unsigned)iCh >= TimMgrCh_SIZE) { DIE(); }
	//チャネルカウンタを返す。
	return pTimMgr->TBL_TimMgrCh[iCh].uCnt;
}
/*--------------------------------------------------------------------------*/
void TimMgr_Exec(ST_TimMgr* pTimMgr) {
	ST_TimMgrCh* pCh;
	int uErr;
	for(pCh = pTimMgr->TBL_TimMgrCh; pCh->uSub; pCh++) {	//1msチャネル(pCh->uSub=0)は含めない。
		//以下の処理で減算できる間、繰り返す。
		// - ループ中に割り込みが発生してpCh->uErrが増えても安全である。
		for(;;) {
ENTER_CS;
			//チャネル誤差項が減算値以上ならば、減算する。
			if((uErr = pCh->uErr - pCh->uSub) >= 0) { pCh->uErr = uErr; }
LEAVE_CS;
			//減算できなければ、抜ける。
			if(uErr < 0) { break; }
			//関数を呼び出す。
			TimMgr_Invoke(pCh);
		}
	}
//{{2017/08/28追加:'TimMgrCh_1exec'を追加しました。
	//上のループを抜けた時、pChは1msチャネルを指しているので、一つ進めて1execチャネルを指すようにする。
	pCh++;
	//1execチャネルの関数を呼び出す。
	TimMgr_Invoke(pCh);
//}}2017/08/28追加:'TimMgrCh_1exec'を追加しました。
}
/*--------------------------------------------------------------------------*/
#ifndef PIECE
void TimMgr_Intr(ST_TimMgr* pTimMgr) {
	ST_TimMgrCh* pCh;
	int uErr;
	//各チャネルについて…
	for(pCh = pTimMgr->TBL_TimMgrCh; pCh->uSub; pCh++) {	//1msチャネル(pCh->uSub=0)は含めない。
		//チャネル誤差項に、加算値を加える。
		uErr = pCh->uErr + pTimMgr->uAdd;
		if(uErr > USHRT_MAX) { uErr = USHRT_MAX; }	//飽和
		pCh->uErr = uErr;
	}
	//1msチャネルのみ、割り込み内で実行する。
	TimMgr_Invoke(pCh);
//{{2017/08/28コメント追加:'TimMgrCh_1exec'を追加しました。
	//1msチャネルの後に1execチャネルも存在していますが、TimMgr_Intr()内では1execチャネルに対して何も行う必要は有りません。
	//1execチャネルは、経過時間に関係無くTimMgr_Exec()から明示的に呼び出すので、チャネル誤差項を増やす必要は無いからです。
//}}2017/08/28コメント追加:'TimMgrCh_1exec'を追加しました。
}
static void TimMgr_Invoke(ST_TimMgrCh* pCh) {
	ST_TimMgrFunc* pFunc;
	//チャネルカウンタを'1'進める。
	pCh->uCnt++;
	//関数チェインの終端に到達するまで…
	for(pFunc = pCh->pNext; pFunc; pFunc = pFunc->pNext) {
		//関数カウンタを'1'増やし、関数インターバルに到達したら…
		if(++pFunc->uCnt >= pFunc->uItv) {
			//関数カウンタをリセットする。
			pFunc->uCnt = 0;
			//関数を呼び出す。
			(*pFunc->fn)(pFunc->arg);
		}
	}
}
#else //PIECE
void TimMgr_Intr(ST_TimMgr* pTimMgr);
/*static*/ void TimMgr_Invoke(ST_TimMgrCh* pCh);
asm("
		.code
		.align		1
		.global		TimMgr_Intr
TimMgr_Intr:
		;//%r12 := pTimMgr
		xld.w		%r4, 65535			;//%r4  := USHRT_MAX
		ld.ub		%r5, [%r12]			;//%r5  := uAdd = pTimMgr->uAdd
		add		%r12, 4				;//%r12 := pCh = pTimMgr->TBL_TimMgrCh
TimMgr_Intr_LOOP:						;//for(;;) {
		ld.uh		%r6, [%r12]+			;//  %r6  := uSub = pCh->uSub, %r12 := &pCh->uErr
		cmp		%r6, 0				;//  if(!uSub) { break }
		jreq		TimMgr_Intr_1MS			;//  
		ld.uh		%r7, [%r12]			;//  %r7  := uErr = pCh->uErr
		add		%r7, %r5			;//  %r7  := uErr += uAdd
		cmp		%r7, %r4			;//  if(uErr > USHRT_MAX) {
		jrle		2				;//     uErr = USHRT_MAX
		 ld.w		%r7, %r4			;//  }
		ld.h		[%r12], %r7			;//  pCh->uErr = uErr
		jp.d		TimMgr_Intr_LOOP		;//}
		add		%r12, 10			;//  %r12 := pCh++	既に2Byte進んでいるので残り10Byte進める。	*delay*
TimMgr_Intr_1MS:						;//
		sub		%r12, 2				;//%r12 := pCh		pCh=&pTimMgr->TBL_TimMgrCh[TimMgrCh_1ms]
TimMgr_Invoke:
		;//%r12 := pCh
		pushn		%r1				;//
		add		%r12, 4				;//%r12     :=               &pCh->uCnt
		ld.w		%r4, [%r12]			;//%r4      := uCnt        =  pCh->uCnt
		add		%r4, 1				;//%r4      := uCnt++							!INTERLOCK!
		ld.w		[%r12]+, %r4			;//pCh->uCnt = uCnt, %r12 := &pCh->pNext
		jp.d		TimMgr_Invoke_START		;//goto L_START
		ld.w		%r0, %r12			;//%r0      := ppNext      = &pCh->pNext				*delay*
TimMgr_Invoke_LOOP:						;//do {
		ld.w		%r4, [%r0]+			;//  %r4    := fn   = pFunc->fn   , %r0  := &pFunc->arg
		ld.w		%r12, [%r0]+			;//  %r12   := arg  = pFunc->arg  , %r0  := &pFunc->uItv
		ld.uh		%r5, [%r0]+			;//  %r5    := uItv = pFunc->uItv , %r0  := &pFunc->uCnt
		ld.uh		%r1, [%r0]			;//  %r1    := uCnt = pFunc->uCnt
		add		%r1, 1				;//  %r1    := uCnt++							!INTERLOCK!
		cmp		%r1, %r5			;//  if(uCnt >= uItv) {
		jrlt		3				;//    %r1  := uCnt = 0
		 call.d		%r4				;//    (*fn)(arg)
		 ld.w		%r1, 0				;//  }									*delay*
		ld.h		[%r0]+, %r1			;//  pFunc->uCnt = uCnt           , %r0  := ppNext = &pFunc->pNext
TimMgr_Invoke_START:						;//  
		ld.w		%r0, [%r0]			;//  %r0    := pFunc = *ppNext
		cmp		%r0, 0				;//} while(pFunc)							!INTERLOCK!
		jrne		TimMgr_Invoke_LOOP		;//
		popn		%r1				;//
		ret						;//
");
#endif//PIECE
/****************************************************************************
 *	使用例
 ****************************************************************************/
#if 0
ST_TimMgr stTimMgr;
int cnt3ms,cnt100ms,cnt1f,cnt5f;
void   myFunc3ms(void* arg){  cnt3ms++;}//─割り込み内で呼び出される。
void myFunc100ms(void* arg){cnt100ms++;}//┐
void    myFunc1f(void* arg){   cnt1f++;}//├割り込み外で呼び出される。
void    myFunc5f(void* arg){   cnt5f++;}//┘
void init(){
#if 0
  //□静的に確保したST_TimMgrFunc構造体を登録する例。組み込み環境等で動的なメモリ確保を避けたい場合はこちら。
  static ST_TimMgrFunc stFunc3ms  ={myFunc3ms  ,NULL,3};
  static ST_TimMgrFunc stFunc100ms={myFunc100ms,NULL,1};
  static ST_TimMgrFunc stFunc1f   ={myFunc1f   ,NULL,1};
  static ST_TimMgrFunc stFunc5f   ={myFunc5f   ,NULL,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
}
int app_main(int argc, char* argv[]) {
  TimMgr_Init(&stTimMgr,30);
  timer_start(1,(void (*)(void*))TimMgr_Intr,&stTimMgr);
  init();
  for(;;){
    schedule();
    surface_clear(&surface,0);
    TimMgr_Exec(&stTimMgr);
    render_printf(&render,10,10,0,3,"%6d",TimMgr_GetCnt(&stTimMgr,TimMgrCh_1f));
    render_printf(&render,10,20,0,3,"%6d",TimMgr_GetCnt(&stTimMgr,TimMgrCh_10ms));
    render_printf(&render,10,30,0,3,"%6d",TimMgr_GetCnt(&stTimMgr,TimMgrCh_100ms));
    render_printf(&render,10,40,0,3,"%6d",TimMgr_GetCnt(&stTimMgr,TimMgrCh_1s));
    render_printf(&render,10,50,0,3,"%6d",TimMgr_GetCnt(&stTimMgr,TimMgrCh_1ms));
    render_printf(&render,50,10,0,3,"%6d %6d",cnt1f,cnt5f);
    render_printf(&render,50,30,0,3,"%6d"    ,cnt100ms);
    render_printf(&render,50,50,0,3,"%6d"    ,cnt3ms);
  }
  return 0;
}
#endif
