/*
 *	clipevtm.c
 *
 *	イベントタイマ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2016 Naoyuki Sawa
 *
 *	* Mon Jun 09 17:53:50 JST 2014 Naoyuki Sawa
 *	- 1st リリース。
 *	- 割り込み外で処理するタイマイベントを、一元管理するための機能です。
 *	  STLプロジェクトの「イベントタイマ更新処理」(SubEventUpdateTimerInfo)の方法を参考にしました。
 *	* Wed Feb 04 21:22:53 JST 2015 Naoyuki Sawa
 *	- Hewは要素数[0]の配列に対応していないので、要素数[1]として調整しました。
 *	  P/ECE開発環境(GCC)やWindows(VC++6.0)は、要素数[0]の配列に対応しているので、関係有りません。
 *	* Sun May 31 14:08:56 JST 2015 Naoyuki Sawa
 *	- dEvtTmrC.exeを作成したことに伴い、dEvtTmrC.exeが生成するデータ形式に対応するよう関数仕様を変更しました。
 *	* Mon Jun 01 00:00:04 JST 2015 Naoyuki Sawa
 *	- ソースコードを整理して、古いコードをコメント化して残していたのを削除しました。
 *	  古いコードを参照する場合は、前回のアーカイブを参照してください。
 *	- ST_EvtTmrCh構造体を変更し、12Byte⇒8Byteにサイズを低減しました。
 *	  dEvtTmrC.exeが出力したバイナリ形式の'EvtTmrNo'と、イベントチャネル番号を1対1に対応付けて使用する場合に、
 *	  イベントチャネル数がかなり多くなるので、ST_EvtTmrCh構造体のサイズを低減する事でRAM節約の効果が高いです。
 *	- 予約済みのイベント番号を、「end=0,loop=USHRT_MAX」⇒「end=USHRT_MAX,loop=(USHRT_MAX-1)」に変更しました。
 *	  アプリケーション定義のイベント番号として、0番を開放しておく方が良いと考えたからです。
 *	* Wed Aug 12 21:51:06 JST 2015 Naoyuki Sawa
 *	- (*pData)[1]の値の意味を変更しました。
 *	  これまでは、全体を一回実行して終了するか(end)、全体をループするか(loop)の指定しか出来ませんでしたが、
 *	  今後は、一部分のループも出来るようになりました。
 *	  詳細は、'/clip/tool/dEvtTmrC/Sample/Sample.txt'を参照してください。
 *	* Thu Aug 13 21:54:21 JST 2015 Naoyuki Sawa
 *	- (*pData)[1]の値の意味を変更しました。
 *	  これまでは、イベントidとして'0'も使用可能でしたが、今後は'0'は使用不可になりました。
 *	  イベントid='0'は、dEvtTmrC.exeによって'no-job'として生成され、当モジュールの処理で破棄されます。
 *	  イベントid='0'が、データ内には出現し得ますが、アプリケーションへ通知される事はありません。
 *	  具体的には、'/clip/tool/dEvtTmrC/winapp.h'の同日のコメントを参照してください。
 *	* Fri Mar 04 21:22:19 JST 2016 Naoyuki Sawa
 *	- 関数名を、旧:EvtTmr_Init()⇒新:EvtTmr_New()、旧:EvtTmr_InitStatic()⇒新:EvtTmr_Init() に変更しました。
 *	  最近作成している他のモジュールの関数名に合わせるためです。
 *	* Sun Sep 04 21:41:17 JST 2016 Naoyuki Sawa
 *	- EvtTmr_Stop()に、64ビット対応のための変更を行いました。
 *	  EvtTmr_Stat()に、64ビット対応のための変更を行いました。
 *	  詳細は、EvtTmr_Stop(),及び,EvtTmr_Stat()内のコメントを参照して下さい。
 */
#include "clip.h"
/****************************************************************************
 *	アプリケーション用関数
 ****************************************************************************/
//イベントタイマを初期化する。
//[in]
//	nEvtTmrCh	イベントタイマチャネル数
ST_EvtTmr* EvtTmr_New(int nEvtTmrCh) {
	ST_EvtTmr* pEvtTmr;
	//イベントタイマ構造体のメモリを確保する。
#ifndef __RENESAS__
	pEvtTmr = malloc(sizeof(ST_EvtTmr) + (sizeof(ST_EvtTmrCh) *  nEvtTmrCh));
#else //__RENESAS__
	pEvtTmr = malloc(sizeof(ST_EvtTmr) + (sizeof(ST_EvtTmrCh) * (nEvtTmrCh - 1/*ST_EvtTmr.EvtTmrCh[0]の分*/)));	//Hewは要素数[0]の配列に対応していないので、要素数[1]として調整しました。
#endif//__RENESAS__
	if(!pEvtTmr) { DIE(); }	//メモリ不足
	//イベントタイマ構造体を初期化する。
	EvtTmr_Init(pEvtTmr, nEvtTmrCh);
	//イベントタイマ構造体のメモリを返す。
	return pEvtTmr;
}
/*--------------------------------------------------------------------------*/
//静的に確保したメモリを使用して、イベントタイマを作成します。
//[in]
//	pEvtTmr		静的に確保したメモリ
//	nEvtTmrCh	イベントタイマチャネル数
//	onEvent		アプリケーション定義のイベント処理関数
//[使用例]
//	│struct {
//	│  ST_EvtTmr   evtTmr;
//	│  ST_EvtTmrCh evtTmrCh[3];
//	│} stEvtTmr;
//	│EvtTmr_Init(&stEvtTmr.evtTmr, 3);
void EvtTmr_Init(ST_EvtTmr* pEvtTmr, int nEvtTmrCh) {
	//イベントタイマ構造体をクリアする。
#ifndef __RENESAS__
	memset(pEvtTmr, 0, sizeof(ST_EvtTmr) + (sizeof(ST_EvtTmrCh) *  nEvtTmrCh));
#else //__RENESAS__
	memset(pEvtTmr, 0, sizeof(ST_EvtTmr) + (sizeof(ST_EvtTmrCh) * (nEvtTmrCh - 1/*ST_EvtTmr.EvtTmrCh[0]の分*/)));	//Hewは要素数[0]の配列に対応していないので、要素数[1]として調整しました。
#endif//__RENESAS__
	//イベントタイマ構造体に、イベントタイマチャネル数を格納する。
	pEvtTmr->nEvtTmrCh = nEvtTmrCh;	//イベントタイマチャネル数
}
/*--------------------------------------------------------------------------*/
//アプリケーション定義のイベント処理関数を追加する。
//[in]
//	pEvtTmr		イベントタイマ
//	pFunc		関数ノード
void EvtTmr_AddFunc(ST_EvtTmr* pEvtTmr, ST_EvtTmrFunc* pFunc) {
	ST_EvtTmrFunc** ppNext;
	//関数チェインの終端ポインタの、変数アドレスを求める。
	for(ppNext = &pEvtTmr->pNext; *ppNext; ppNext = &(*ppNext)->pNext) {
		//同じ関数ノードを二回登録しようとしたら、エラー停止する。
		if(*ppNext == pFunc) { DIE(); }
	}
	//追加する関数ノードのポインタを終端にする。
	pFunc->pNext = NULL;
	//関数ノードを追加する。
	*ppNext = pFunc;
}
/*--------------------------------------------------------------------------*/
//イベントタイマチャネルを開始する。
//[in]
//	pEvtTmr		イベントタイマ
//	iEvtTmrCh	イベントタイマチャネル番号
//	pData		イベント制御データ (Wait,Event,Wait,Event,…の並び)
//[note]
//	- イベントタイマチャネルが動作中だった場合、リスタートする。
void EvtTmr_Start(ST_EvtTmr* pEvtTmr, int iEvtTmrCh, const unsigned short (*pData)[2/*Wait,Event*/]) {
	ST_EvtTmrCh* pEvtTmrCh;
	//イベントタイマチャネル番号が正常であることを確認する。
	if((unsigned)iEvtTmrCh >= (unsigned)pEvtTmr->nEvtTmrCh) { DIE(); }
	//イベントタイマチャネルを取得する。
	pEvtTmrCh = &pEvtTmr->EvtTmrCh[iEvtTmrCh];
	//イベントタイマチャネルを開始する。
	pEvtTmrCh->pData =   pData;
	pEvtTmrCh->Wait  = (*pData)[0/*Wait*/];	//最初のイベントの待ち時間を取得する。
}
/*--------------------------------------------------------------------------*/
//イベントタイマチャネルを確実に停止する。
//[in]
//	pEvtTmr		イベントタイマ
//	iEvtTmrCh	イベントタイマチャネル番号
//[out]
//	戻り値		イベントタイマチャネルが動作中だった場合、イベントタイマチャネルを停止し、0以外を返す。
//			イベントタイマチャネルが停止中だった場合、何もせず、0を返す。
//[note]
//	* 「イベントタイマチャネルを確実に停止する前の状態を返す」ようにした理由は、以下のとおりです。
//	- 実際のアプリケーションの中で、下記のようなパターンが多く現れることに気付きました:
//	  <例>
//	    EvtTmr_Start(pEvtTmr,EvtTmrCh_xxx,pData);
//	    …
//	    if(EvtTmr_Stat(pEvtTmr,EvtTmrCh_xxx)){//ﾀｲﾑｱｳﾄしていなければ…
//	        EvtTmr_Stop(pEvtTmr,EvtTmrCh_xxx);//停止する
//	        {{ﾀｲﾑｱｳﾄ時に行うはずだった処理を､ここで行う}}
//	    }else{//ﾀｲﾑｱｳﾄしていたら…
//	        {{ﾀｲﾑｱｳﾄ時に行った処理の後始末を､ここで行う}}//<例>ﾀｲﾑｱｳﾄで減音した場合､ここで減音復帰する､等
//	    }
//	- 「イベントタイマチャネルを確実に停止する前の状態を返す」の戻り値を利用すると、下記のように書けます:
//	  <例>
//	    EvtTmr_Start(pEvtTmr,EvtTmrCh_xxx,pData);
//	    …
//	    if(EvtTmr_Stop(pEvtTmr,EvtTmrCh_xxx)){//確実に停止する｡停止前にﾀｲﾑｱｳﾄしていなければ…
//	        {{ﾀｲﾑｱｳﾄ時に行うはずだった処理を､ここで行う}}
//	    }else{//ﾀｲﾑｱｳﾄしていたら…
//	        {{ﾀｲﾑｱｳﾄ時に行った処理の後始末を､ここで行う}}//<例>ﾀｲﾑｱｳﾄで減音した場合､ここで減音復帰する､等
//	    }
int EvtTmr_Stop(ST_EvtTmr* pEvtTmr, int iEvtTmrCh) {
	int iResult;
	ST_EvtTmrCh* pEvtTmrCh;
	//イベントタイマチャネル番号が正常であることを確認する。
	if((unsigned)iEvtTmrCh >= (unsigned)pEvtTmr->nEvtTmrCh) { DIE(); }
	//イベントタイマチャネルを取得する。
	pEvtTmrCh = &pEvtTmr->EvtTmrCh[iEvtTmrCh];
	//イベントタイマチャネルの状態を取得する。
//{{2016/09/04変更:EvtTmr_Stop()に、64ビット対応のための変更を行いました。
//	iResult = (int)pEvtTmrCh->pData;
//↓2016/09/04変更:EvtTmr_Stop()に、64ビット対応のための変更を行いました。
#ifndef _WIN64
	iResult = (int)pEvtTmrCh->pData;	//32ビットならば、ポインタを(int)にキャストするだけで良い。32ビットでも、下記の64ビットと同じ処理にしても構わないのだが、ポインタを(int)にキャストするだけの方が効率が良いコードが生成される(特にP/ECEの場合)ので、敢えて条件コンパイルで分ける事にした。
#else //_WIN64
	iResult = !!pEvtTmrCh->pData;		//64ビットならば、明示的にポインタがNULLでないかを、判定する必要が有る。なぜならば、ポインタを(int)にキャストすると、ポインタの上位32ビットが失われてしまい、万一、非NULLポインタの下位32ビットが偶然0だった場合に、間違った結果になってしまうからである。
#endif//_WIN64
//}}2016/09/04変更:EvtTmr_Stop()に、64ビット対応のための変更を行いました。
	//イベントタイマチャネルを確実に停止する。
	pEvtTmrCh->pData = NULL;
//不要	pEvtTmrCh->Wait  = 0;
	//イベントタイマチャネルが動作中だったならば0以外、停止中だったならば0を返す。
	return iResult;
}
/*--------------------------------------------------------------------------*/
//イベントタイマチャネルを全て停止する。
//[in]
//	pEvtTmr		イベントタイマ
void EvtTmr_StopAll(ST_EvtTmr* pEvtTmr) {
	memset(pEvtTmr->EvtTmrCh, 0, sizeof(ST_EvtTmrCh) * pEvtTmr->nEvtTmrCh);
}
/*--------------------------------------------------------------------------*/
//イベントタイマチャネルの状態を取得する。
//[in]
//	pEvtTmr		イベントタイマ
//	iEvtTmrCh	イベントタイマチャネル番号
//[out]
//	戻り値		イベントタイマチャネルが動作中ならば0以外、停止中ならば0を返す。
int EvtTmr_Stat(ST_EvtTmr* pEvtTmr, int iEvtTmrCh) {
	ST_EvtTmrCh* pEvtTmrCh;
	//イベントタイマチャネル番号が正常であることを確認する。
	if((unsigned)iEvtTmrCh >= (unsigned)pEvtTmr->nEvtTmrCh) { DIE(); }
	//イベントタイマチャネルを取得する。
	pEvtTmrCh = &pEvtTmr->EvtTmrCh[iEvtTmrCh];
	//イベントタイマチャネルが動作中ならば0以外、停止中ならば0を返す。
//{{2016/09/04変更:EvtTmr_Stat()に、64ビット対応のための変更を行いました。
//	return (int)pEvtTmrCh->pData;
//↓2016/09/04変更:EvtTmr_Stat()に、64ビット対応のための変更を行いました。
#ifndef _WIN64
	return (int)pEvtTmrCh->pData;	//32ビットならば、ポインタを(int)にキャストするだけで良い。32ビットでも、下記の64ビットと同じ処理にしても構わないのだが、ポインタを(int)にキャストするだけの方が効率が良いコードが生成される(特にP/ECEの場合)ので、敢えて条件コンパイルで分ける事にした。
#else //_WIN64
	return !!pEvtTmrCh->pData;	//64ビットならば、明示的にポインタがNULLでないかを、判定する必要が有る。なぜならば、ポインタを(int)にキャストすると、ポインタの上位32ビットが失われてしまい、万一、非NULLポインタの下位32ビットが偶然0だった場合に、間違った結果になってしまうからである。
#endif//_WIN64
//}}2016/09/04変更:EvtTmr_Stat()に、64ビット対応のための変更を行いました。
}
/*--------------------------------------------------------------------------*/
//イベントタイマの周期処理を行う。
//[in]
//	pEvtTmr		イベントタイマ
//[note]
//	- アプリケーション定義のイベント処理関数の中で、任意のチャネル(自身含む)に対して、
//	  EvtTmr_Start(),及び,EvtTmr_Stop()を呼び出すことも、安全です。
void EvtTmr_Exec(ST_EvtTmr* pEvtTmr) {
	int iEvtTmrCh, iEvent;
	ST_EvtTmrCh* pEvtTmrCh;
	ST_EvtTmrFunc* pFunc;
	//各イベントタイマチャネルについて…
	for(iEvtTmrCh = 0; iEvtTmrCh < pEvtTmr->nEvtTmrCh; iEvtTmrCh++) {
		//イベントタイマチャネルを取得する。
		pEvtTmrCh = &pEvtTmr->EvtTmrCh[iEvtTmrCh];
		//イベントタイマチャネルが動作中である間…
		while(pEvtTmrCh->pData) {
			//待ち時間の残数が有れば、次回のために待ち時間の残数を減らす。
			if(pEvtTmrCh->Wait) { pEvtTmrCh->Wait--; break; }	//ここまで
			//イベント番号を取得し、データ再生位置を進める。
			iEvent = (short)(*pEvtTmrCh->pData++)[1/*Event*/];		//'ダミー(0),又は,アプリケーション定義のイベント番号(1〜)'か、'終了(-1),又は,次の命令を基準としてループ戻り位置の命令へのマイナス距離(-2〜)'かを判定するために、符号付きで取得する。
			//'ダミー(0),又は,アプリケーション定義のイベント番号(1〜)'ならば…
			if(iEvent >= 0) {
			    //{{この処理順必須
				//次のイベントの待ち時間を取得する。
				pEvtTmrCh->Wait = (*pEvtTmrCh->pData)[0/*Wait*/];	//onEvent()の中で、EvtTmr_Start(),又は,EvtTmr_Stop()が呼ばれた場合は、pEvtTmrCh->Waitが上書きされる。
				//'アプリケーション定義のイベント番号(1〜)'ならば…
				if(iEvent) {
					//関数チェインの終端に到達するまで…
					for(pFunc = pEvtTmr->pNext; pFunc; pFunc = pFunc->pNext) {
						//関数を呼び出す。
						(*pFunc->onEvent)(iEvtTmrCh, iEvent);
					}
				}
			    //}}この処理順必須
			//'終了(-1)'ならば…
			} else if(iEvent == -1) {
				//イベントタイマチャネルを停止する。
				EvtTmr_Stop(pEvtTmr, iEvtTmrCh);
			//'次の命令を基準としてループ戻り位置の命令へのマイナス距離(-2〜)'ならば…
			} else {
				//イベントタイマチャネルを、ループ戻り位置の命令からリスタートする。
				EvtTmr_Start(pEvtTmr, iEvtTmrCh, pEvtTmrCh->pData + iEvent/*次の命令を基準としてループ戻り位置の命令へのマイナス距離*/);
			}
		}
	}
}
/****************************************************************************
 *	dEvtTmrC.exeが出力したバイナリ形式を扱うためのユーティリティ関数
 ****************************************************************************/
#ifdef  USE_EVTTMR_BIN
void EvtTmr_StartNo(ST_EvtTmr* pEvtTmr, int iEvtTmrCh, const void* pEvtTmrBin, int iEvtTmrNo) {
	const int* const TBL_EvtTmrNo = pEvtTmrBin;												//バイナリの先頭にアドレステーブルが有る。アドレスはデータ本体(HALF[2]配列)のインデクスである。
	const int nEvtTmrNo = TBL_EvtTmrNo[0];													//アドレステーブルの先頭[0]に要素数が入っている。要素数はそれ自身([0])も含む。
	const unsigned short (* const TBL_EvtTmr)[2/*Wait,Event*/] = (const unsigned short (*)[2/*Wait,Event*/])&TBL_EvtTmrNo[nEvtTmrNo];	//アドレステーブルの直後からデータ本体(HALF[2]配列)が開始する。
	if((iEvtTmrNo <= 0) || (iEvtTmrNo >= nEvtTmrNo)) { DIE(); }
	EvtTmr_Start(pEvtTmr, iEvtTmrCh, &TBL_EvtTmr[TBL_EvtTmrNo[iEvtTmrNo]]);
}
#endif//USE_EVTTMR_BIN
/****************************************************************************
 *	サンプル
 ****************************************************************************/
#if 0
const unsigned short Data0[][2]={//ｲﾍﾞﾝﾄﾀｲﾏﾁｬﾈﾙ#0のﾃﾞｰﾀ
{SEC(0),10}, //開始直後に､ｺﾏﾝﾄﾞ番号(10)を実行する｡
{SEC(2),11}, // 2秒待って､ｺﾏﾝﾄﾞ番号(11)を実行する｡
{SEC(1),12}, // 1秒待って､ｺﾏﾝﾄﾞ番号(12)を実行する｡
{SEC(0),13}, //その後すぐ､ｺﾏﾝﾄﾞ番号(13)を実行する｡
{SEC(3),-5}};// 3秒待って､先頭へﾙｰﾌﾟする｡
const unsigned short Data1[][2]={//ｲﾍﾞﾝﾄﾀｲﾏﾁｬﾈﾙ#1のﾃﾞｰﾀ
{SEC(9),20}, // 9秒待って､ｺﾏﾝﾄﾞ番号(20)を実行する｡
{SEC(0),-1}};//その後すぐ､終了する｡
void onEvent(int iEvtTmrCh,int iEvent){//ｱﾌﾟﾘｹｰｼｮﾝ定義のｲﾍﾞﾝﾄ処理関数
  TRACE("%d:%d:%d\n",now/FPS,iEvtTmrCh,iEvent);}
ST_EvtTmrFunc stFunc={onEvent};
void Test() {
  ST_EvtTmr* pEvtTmr=EvtTmr_New(2);
  EvtTmr_AddFunc(pEvtTmr,&stFunc);
  EvtTmr_Start(pEvtTmr,0,Data0);//ｲﾍﾞﾝﾄﾀｲﾏﾁｬﾈﾙ#0を開始する｡
  EvtTmr_Start(pEvtTmr,1,Data1);//ｲﾍﾞﾝﾄﾀｲﾏﾁｬﾈﾙ#1を開始する｡
  for(;;) {//ﾒｲﾝﾙｰﾌﾟ
    schedule();
    EvtTmr_Exec(pEvtTmr);//ｲﾍﾞﾝﾄﾀｲﾏの周期処理を行う｡
  }
}
#endif
