/*
 *	cliptimm.c
 *
 *	タイマ管理
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2016 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フィールドを追加しました。
 *	  これまでは、引数を取る関数をタイマに登録するためには、中継関数を経由する必要が有りました。
 *	  今回の変更によって、引数を取る関数も直接、タイマに登録する事が出来るようになりました。
 */
#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
//不要	pCh->uSub = 0;				//1ms
}
/*--------------------------------------------------------------------------*/
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;
    //}}この順序で処理すれば、既にタイマが開始していても、割り込み禁止が不要である。
}
/*--------------------------------------------------------------------------*/
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);
		}
	}
}
/*--------------------------------------------------------------------------*/
#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);
}
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(){
  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);
}
void app_main(){
  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);
  }
}
#endif
