/*	
 *	cliptask.h
 *
 *	タスクスケジューラ (コンテキストスイッチ版)
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2006-2014 Naoyuki Sawa
 *
 *	* Fri Jun 09 22:27:00 JST 2006 Naoyuki Sawa
 *	- 1st リリース。
 *	  旧いcliptask.hは、keepフォルダに保存しました。
 *	* Wed Jun 14 00:44:00 JST 2006 Naoyuki Sawa
 *	- task_kill()を追加しました。
 *	* Thu Sep 11 21:21:57 JST 2014 Naoyuki Sawa
 *	- task_yield()を追加しました。
 *	* Fri Sep 12 22:07:53 JST 2014 Naoyuki Sawa
 *	- task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *	  応用例は、task_yield()の[note]の、「タスクスケジューラを、アイドルスレッドと併用して、〜」を参照してください。
 *	* Thu Oct 30 00:04:33 JST 2014 Naoyuki Sawa
 *	- 自動リセットイベントに対してevent_pulse()を行った時、最も高い実行優先度を持つタスクを起床すべきところ、
 *	  先着順で封鎖タスクの先頭に登録されているタスクを起床していたという、問題を修正しました。
 *	  □サンプル
 *	  │EVENT* event;
 *	  │TASK *task1,*task2;
 *	  │int task_proc(void* param) {
 *	  │  event_wait(event);
 *	  │  TRACE("%s\n",param);
 *	  │  return 0;
 *	  │}
 *	  │void app_main() {
 *	  │  event=event_create(0);
 *	  │  task1=task_create(1,task_proc,"task1",0);//実行優先度=低
 *	  │  task2=task_create(2,task_proc,"task2",0);//実行優先度=高
 *	  │  event_pulse(event);//⇒修正前はtask1が起床していた。修正後はtask2が起床する。
 *	  │  event_pulse(event);//⇒修正前はtask2が起床していた。修正後はtask1が起床する。
 *	  │  for(;;) { schedule(); }
 *	  │}
 *	* Wed Nov 18 21:37:53 JST 2015 Naoyuki Sawa
 *	- 'extern "C" {'〜'}'で囲みました。.cppを含むWin32プロジェクトからもインクルード出来るようにするためです。
 */
#ifndef __CLIP_TASK_H__
#define __CLIP_TASK_H__

#ifdef  __cplusplus
extern "C" {
#endif//__cplusplus

/****************************************************************************
 *	タスク
 ****************************************************************************/

/* タスク構造体  */
typedef struct _TASK {
	/*{{レイアウト固定*/
	int* sp;			/* %sp保存 */
	/*}}レイアウト固定*/
	int suspend;			/* サスペンド回数 (1以上 = 停止, 0 = 動作, 0未満 = 設定不可) */
	int priority;			/* 実行優先度 (INT_MAX = 最高 〜 0 = 通常 〜 INT_MIN = 最低) */
	int exit_code;			/* 終了コード */
	LIST_ENTRY blocked_task_list;	/* 封鎖タスクリスト                                                           */
					/* * リストは空か、または、唯一の終了待ちタスクが登録されています。           */
					/*   二つ以上の終了待ちタスクが登録されていることは、有り得ません。           */
					/*   - このタスクは終了しておらず、終了待ちタスクは無い → 空リスト           */
					/*   - このタスクは終了しておらず、終了待ちタスクは有る → 終了待ちタスク登録 */
					/*   - このタスクは終了済みであり、終了待ちタスクは無い → 空リスト           */
					/*   - このタスクは終了済みであり、終了待ちタスクは有る → 終了待ちタスク登録 */
	LIST_ENTRY task_list_entry;	/* 各種タスクリストへの登録用                                                          */
					/* * このタスクが『終了タスク』である場合を除き、必ずタスクリストに登録されています。  */
					/*   すなわち、task_list_entry.Flink!=NULLであり、後述の『終了タスク』と区別できます。 */
					/*   登録先のタスクリストは、以下の通りです。                                          */
					/*   - 実行可能である場合、『実行可能タスクリスト』(ready_task_list)                   */
					/*   - タスク終了待ちである場合、タスクの『封鎖タスクリスト』(TASK.blocked_task_list)  */
					/*   - イベント待ちである場合、イベントの『封鎖タスクリスト』(EVENT.blocked_task_list) */
					/* * このタスクが『終了タスク』(終了済み、かつ、終了待ちタスクが未登録)である場合、    */
					/*   明示的にtask_list_entry.Flink=NULLとして、『終了タスク』であることを示します。    */
} TASK;

/* タスクスケジューラを初期化します。
 * [in]
 *    //{{2014/09/12追加:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *	priority	システムアイドルタスクの実行優先度。
 *    //}}2014/09/12追加:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 * [note]
 *	* タスク機能を利用するアプリケーションが、最初に一度だけ呼び出してください。
 *	  この関数を呼び出さずにタスク機能を利用しようとすると、異常動作となります。
 *
 *		int main() {
 *			...
 *    //{{2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *    //		task_init(); // タスク機能を利用するならば、必要!!
 *    //↓2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *			task_init(INT_MIN); // タスク機能を利用するならば、必要!!
 *    //}}2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *			...
 *		}
 *
 *	* task_init()と対になる、タスク機能のクリーンアップ関数はありません。
 *	  task_init()を呼び出したアプリケーションが、終了前に呼び出す必要のある関数はありません。
 *	  また、いくつかのタスクが残ったままで、アプリケーションを終了しても構いません。
 *
 *	* この関数を呼び出したコンテキストを、システムアイドルタスクとして登録します。
 *    //{{2014/09/12削除:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *    //  システムアイドルタスクの実行優先度は、INT_MIN(アイドル優先度)に固定で、変更できません。
 *    //}}2014/09/12削除:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *
 *	* システムアイドルタスクは、終了してはいけません。(task_exit()呼び出し不可)
 *	  誤ってシステムアイドルタスクを終了してしまった場合の動作は、未定義です。
 *
 *		int main() {
 *			...
 *    //{{2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *    //		task_init();
 *    //↓2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *			task_init(INT_MIN);
 *    //}}2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *			...
 *			task_exit(0); // 誤り!!
 *		}
 */
//{{2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
//void task_init();
//↓2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
void task_init(int priority);
//}}2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。

/* 実行中タスクを取得します。
 * [out]
 *	戻り値		実行中タスク。
 * [note]
 *	* task_init()を呼び出した後は、常に何らかのタスクが実行中です。
 *	  task_get_current()がNULLを返すことは、絶対にありません。
 */
TASK* task_get_current();

/* タスクを作成します。
 * [in]
 *	priority	実行優先度。
 *	task_proc	アプリケーション定義のタスク関数。
 *	param		タスク関数に引き渡す、アプリケーション定義のパラメータ。
 *	stack_size	スタックサイズ。(バイト数)
 *			0を指定すると、既定のスタックサイズを使用します。
 * [out]
 *	戻り値		作成したタスク。
 * [note]
 *	* タスクの実行優先度は、タスク作成時に指定し、以後、変更することはできません。
 *	  タスクの実行優先度を変更する関数を用意することは簡単ですが、実際のアプリケーションにおいて、
 *	  タスク作成後に実行優先度の変更が必要となる場面はまず無いと考えて、あえて用意しませんでした。
 *
 *	* タスク作成直後の、サスペンド回数の初期値は0です。(=サスペンドしていない)
 *	  すなわち、task_create()を呼び出したタスクよりも、作成されたタスクの実行優先度が高い場合、
 *	  task_create()が処理を返すのは、作成されたタスクが封鎖されるか、または、終了後となります。
 *
 *		int main() {
 *			...
 *    //{{2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *    //		task_init();                                 // 実行優先度=INT_MIN
 *    //↓2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *			task_init(INT_MIN);                          // 実行優先度=INT_MIN
 *    //}}2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *			...
 *			task1 = task_create(1, task1_proc, NULL, 0); // 実行優先度=1
 *			// この時点で、task1は封鎖されているか、既に終了している。
 *			...
 *		}
 *
 *		int task1_proc(void* param) {                        // 実行優先度=1
 *			...
 *			task2 = task_create(0, task2_proc, NULL, 0); // 実行優先度=0
 *			// この時点で、task2の処理は、まだ実行されていない。
 *			...
 *		}
 *
 *	* 初期状態でサスペンドしているタスクを作成する方法は、用意していません。
 *	  もし必要ならば、タスク関数の先頭に自分自身をサスペンドする処理を配置することで実現できます。
 *
 *		int task_proc(void* param) {
 *			task_suspend(task_get_current());
 *			...
 *		}
 */
TASK* task_create(int priority, int (*task_proc)(void* param), void* param, int stack_size);

/* タスクを終了します。
 * [in]
 *	exit_code	終了コード。
 * [note]
 *	* この関数は、処理を返しません。
 *
 *	* アプリケーション定義のタスク関数は、以下の二種類の方法でタスクを終了できます。
 *
 *		1. 終了コードを指定して、task_exit()を呼び出す。
 *
 *			int task_proc(void* param) {
 *				...
 *				task_exit(result);
 *			}
 *
 *		2. 終了コードを伴って、タスク関数からreturnする。
 *
 *			int task_proc(void* param) {
 *				...
 *				return result;
 *			}
 */
void task_exit(int exit_code) __attribute__((noreturn));

/* タスクの終了を待って、リソースを回収します。
 * [in]
 *	task		待たれるタスク。
 * [out]
 *	戻り値		終了コード。
 * [note]
 *	* 自分自身の終了を待つことはできません。
 *	  自分自身の終了を待とうとすると、アサートします。
 *
 *		reulst = task_wait(task_get_current()); // 誤り!!
 *
 *	* task_wait()を呼び出すことで、終了したタスクのリソースが回収されます。
 *	  task_wait()を呼び出さないと、終了したタスクのリソースが回収されずに、残ってしまいます。
 *	  繰り返し作成するタスクに対しては、task_wait()を呼び出して、リソースを回収してください。
 *
 *		for(;;) {
 *			...
 *			task = task_create(0, task_proc, NULL, 0);
 *			...
 *			result = task_wait(task); // 必要!!
 *			...
 *		}
 *
 *	* 一つのタスクの終了を、複数のタスクが待つことはできません。
 *	  一つのタスクの終了を、複数のタスクが待とうとすると、アサートします。
 *
 *		TASK* task4;
 *
 *		int task1_proc(void* param) {
 *			...
 *			task4 = task_create(0, task4_proc, NULL, 0);
 *			...
 * 		}
 *
 *		int task2_proc(void* param) {
 *			...
 *			result = task_wait(task4); // どちらか一方が誤り!!
 *			...
 * 		}
 *
 *		int task3_proc(void* param) {
 *			...
 *			result = task_wait(task4); // どちらか一方が誤り!!
 *			...
 * 		}
 */
int task_wait(TASK* task);

/* タスクの終了を待たずに、リソースを回収します。
 * [in]
 *	task		強制終了されるタスク。
 * [note]
 *	* 自分自身を強制終了することはできません。
 *	  自分自身を強制終了しようとすると、アサートします。
 *
 *		task_kill(task_get_current()); // 誤り!!
 *
 *	* 既に終了しているタスクに対して、task_kill()を呼び出すこともできます。
 *	  強制終了されるタスクが、既に終了しているか否かによって、task_kill()の結果に違いはありません。
 *
 *	* タスク1がタスク2の終了を待っているときに、タスク3がタスク2を強制終了することはできません。
 *	  このような仕様とした理由は、タスク1が呼び出したtask_wait()の戻り値が定義できないためです。
 *
 *		int task1_proc(void* param) {
 *			...
 *			result = task_wait(task2);
 *			...
 *		}
 *
 *		int task3_proc(void* param) {
 *			...
 *			task_kill(task2); // 誤り!!
 *			...
 *		}
 *
 *	  この問題を避けるには、各タスク設計時に、task_wait()によってリソース回収されるべきタスクか、
 *	  または、task_kill()によってリソース回収されるべきタスクかを、明確にしておく必要があります。
 *
 *	* タスク1がタスク2の終了を待ってリソースを回収するために、task_wait()を呼び出して封鎖されているとき、
 *	  タスク3がタスク1を強制終了すると、タスク1はタスク2の封鎖タスクリストから登録解除されます。
 *	  アプリケーションが、このケースを意識して、別の手段によってタスク2のリソースを回収しない限り、
 *	  タスク2のリソースがリークしてしまいます。
 *
 *		int task1_proc(void* param) {
 *			...
 *			result = task_wait(task2); // この呼び出しが処理を返す前に...
 *			...
 *		}
 *
 *		int task3_proc(void* param) {
 *			...
 *			task_kill(task1);          // この呼び出しを行うと、task2がリソースリーク!!
 *			...
 *		}
 *
 *	  この問題は、タスクのリソース回収に限らず、task1がリソース回収処理を行うタスクである場合に、
 *	  その処理を行う前に強制終了されると、リソースがリークしてしまう問題であると言えます。
 *	  ライブラリ側で特に対策は設けていませんので、アプリケーション側にて注意してください。
 */
void task_kill(TASK* task);

/* タスクのサスペンド回数を増やします。
 * [in]
 *	task		サスペンド回数を増やすタスク。
 * [note]
 *	* サスペンド回数INT_MAXを上回って、サスペンド回数を増やすことはできません。
 */
void task_suspend(TASK* task);

/* タスクのサスペンド回数を減らします。
 * [in]
 *	task		サスペンド回数を減らすタスク。
 * [note]
 *	* サスペンド回数0を下回って、サスペンド回数を減らすことはできません。
 */
void task_resume(TASK* task);

//{{2014/09/11追加:task_yield()を追加しました。
/* 現在の『実行中タスク』と同じ実行優先度を持つ、次の『実行可能タスク』へ、処理を切り替えます。
 * [note]
 *	* 当関数は、具体的には、以下の処理を行います。
 *	  1. 現在の『実行中タスク』を、『実行可能タスクリスト』から一時的に取り外します。
 *	     (当関数が呼ばれた時に、『実行中タスク』は、『実行可能タスクリスト』の先頭に有るはずなので、一時的に取り外す。)
 *	  2. 現在の『実行中タスク』を、『実行可能タスクリスト』の末尾に追加します。
 *	     (1.と2.の結果として、現在の『実行中タスク』が、『実行可能タスクリスト』の先頭から末尾へ、移動したことになる。)
 *	  3. タスクスケジューリングを実行します。
 *	     『実行可能タスクリスト』の中には、現在の『実行中タスク』より高い実行優先度を持つタスクは存在しないはずなので、
 *	     3.の結果として、現在の『実行中タスク』と同じ実行優先度を持つ、次の『実行可能タスク』へ、処理が切り替わります。
 *	     『実行可能タスクリスト』が空であるか、又は、現在の『実行中タスク』未満の実行優先度を持つタスクしか無い場合は、
 *	     ただちに、再度、現在の『実行中タスク』へ、処理が切り替わります。
 *
 *	* 当関数を利用すると、ラウンドロビンを簡単に実装することができます。
 *	  (これまでは、イベント待ちや、タスクのサスペンド回数を使って実装する必要が有りましたが、今後は簡単に実装できます。)
 *
 *		//例1:メインスレッドを含むラウンドロビン
 *		static int task_proc(void* param){
 *		  for(;;){
 *		    TRACE("task#%d   now=%d\n",(int)param,now);
 *		    task_yield();
 *		  }
 *		}
 *		void app_main(){
 *    //{{2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *    //	  task_init();//メインスレッドがシステムアイドルタスクとなる
 *    //	  task_create(INT_MIN,task_proc,(void*)0,0);
 *    //	  task_create(INT_MIN,task_proc,(void*)1,0);
 *    //↓2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *		  task_init(0);//メインスレッドがシステムアイドルタスクとなる	┐task_yield()を用いてラウンドロビンを行う場合は、全てのタスクの優先度を等しくすること。
 *		  task_create(0,task_proc,(void*)0,0);				│左の例では、全てのタスクの優先度を(0)とした。
 *		  task_create(0,task_proc,(void*)1,0);				┘等しければ何でも良く、(0)でなくても構わない。
 *    //}}2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *		  for(;;){
 *		    schedule();
 *		    TRACE("app_main now=%d\n",now);
 *		    task_yield();
 *		  }
 *		}
 *		//↓結果
 *		//app_main now=1
 *		//task#0   now=1
 *		//task#1   now=1
 *		//app_main now=2
 *		//task#0   now=2
 *		//task#1   now=2
 *		//app_main now=3
 *		//task#0   now=3
 *		//task#1   now=3
 *		//…
 *
 *	* タスクスケジューラを、アイドルスレッドと併用して、その中で当関数を利用するのも便利だと思います。
 *	  複数のアイドル処理を、アイドルスレッドの中で実行することができます。
 *
 *		//例2:メインスレッドを含まないラウンドロビン
 *		static int task_proc(void* param){
 *		  static int step[2];
 *		  for(;;){
 *		    TRACE("task#%d step=%d\n",(int)param,step[(int)param]++);
 *		    task_yield();
 *		  }
 *		}
 *		static int idle_proc(void* param){
 *    //{{2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *    //	  task_init();//アイドルスレッドがシステムアイドルタスクとなる
 *    //	  task_create(INT_MIN,task_proc,(void*)0,0);
 *    //	  task_create(INT_MIN,task_proc,(void*)1,0);
 *    //	  task_suspend(task_get_current());
 *    //↓2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *		  task_init(1);//アイドルスレッドがシステムアイドルタスクとなる
 *		  task_create(0,task_proc,(void*)0,0);//システムアイドルタスクの方が実行優先度が高いので、まだ開始しない。
 *		  task_create(0,task_proc,(void*)1,0);//システムアイドルタスクの方が実行優先度が高いので、まだ開始しない。
 *		  task_suspend(task_get_current());//システムアイドルスレッドをサスペンドしたので、上記2タスクが開始する。
 *    //}}2014/09/12変更:task_init()の引数priorityを追加し、システムアイドルタスクの実行優先度を設定できるようにしました。
 *		  DIE();//NOTREACHED
 *		}
 *		void app_main(){
 *		  idle_init(idle_proc,NULL,0);
 *		  for(;;){
 *		    schedule();//この中ではyield()でなくidle_yield()を呼ぶこと
 *		  }
 *		}
 *		//↓結果
 *		//task#0 step=0
 *		//task#1 step=0
 *		//task#0 step=1
 *		//task#1 step=1
 *		//task#0 step=2
 *		//task#1 step=2
 *		//…
 */
void task_yield();
//}}2014/09/11追加:task_yield()を追加しました。

/****************************************************************************
 *	イベント
 ****************************************************************************/

/* イベント構造体  */
typedef struct _EVENT {
	int signal;			/* イベント状態 (0 = 非シグナル状態, 1 = シグナル状態) */
	int manual_reset;		/* リセット区分 (0 = 自動リセット, 1 = 手動リセット) */
	LIST_ENTRY blocked_task_list;	/* 封鎖タスクリスト */
} EVENT;

/* イベントを作成します。
 * [in]
 *	manual_reset	リセット区分。
 *			0を指定すると、自動リセットイベントを作成します。
 *			1を指定すると、手動リセットイベントを作成します。
 * [out]
 *	戻り値		作成したイベント。
 * [note]
 *	* イベントの初期状態は、『非シグナル状態』です。
 *
 *	* event_create()の引数で、イベントの初期状態を指定する方法は、あえて採りませんでした。
 *	  初期状態で『シグナル状態』のイベントが必要となるケースは、まず無いと考えたからです。
 *	  もし必要ならば、event_create()の直後に、event_set()を呼び出してください。
 *
 *		event = event_create(0または1); // 非シグナル状態
 *		event_set(event);               //   シグナル状態
 */
EVENT* event_create(int manual_reset);

/* イベントを削除します。
 * [in]
 *	event		削除するイベント。
 * [note]
 *	* イベント待ちの『封鎖タスク』が残っている場合、イベントを削除することはできません。
 *	  イベントを削除しようとすると、アサートします。
 *
 *		int task1_proc(void* param) {
 *			...
 *			event = event_create(0または1);
 *			task2 = task_create(0, task2_proc, NULL, 0);
 *			event_delete(event); // 誤り!!
 *			...
 *		}
 *
 *		int task2_proc(void* param) {
 *			...
 *			event_wait(event);
 *			...
 *		}
 */
void event_delete(EVENT* event);

/* イベントを待ちます。
 * [in]
 *	event		待たれるイベント。
 */
void event_wait(EVENT* event);

/* イベントを発行します。(セット)
 * [in]
 *	event		発行するイベント。
 * [note]
 *	* イベント状態を『シグナル状態』にします。
 *	  起床される『封鎖タスク』の数とイベント状態の遷移は、リセット区分と『封鎖タスク』の有無に依存します。
 *	  概ね、Windowsのイベント関数と同じ挙動です。Windowsのイベント関数仕様を参照してください。
 */
void event_set(EVENT* event);

/* イベントを発行します。(パルス)
 * [in]
 *	event		発行するイベント。
 * [note]
 *	* イベント状態を『シグナル状態』にし、直ちに『非シグナル状態』に戻します。
 *	  開放される『封鎖タスク』の数は、リセット区分に依存します。
 *	  概ね、Windowsのイベント関数と同じ挙動です。Windowsのイベント関数仕様を参照してください。
 */
void event_pulse(EVENT* event);

/* イベントを非シグナル状態にします。
 * [in]
 *	event		非シグナル状態にするイベント。
 */
void event_reset(EVENT* event);

#ifdef  __cplusplus
}//extern "C"
#endif//__cplusplus

#endif /*__CLIP_TASK_H__*/
