//
//	cliprrs.cs
//
//	Round Robin Scheduler (RRS)
//
//	CLiP - Common Library for P/ECE
//	Copyright (C) 2017 Naoyuki Sawa
//
//	* Tue Mar 07 22:38:22 JST 2017 Naoyuki Sawa
//	- 1st リリース。
//	* Wed Mar 08 22:12:10 JST 2017 Naoyuki Sawa
//	- Unity環境においてサブスレッドで例外が発生して停止すると、RRSが回らなくなりUnityエディタがハングアップしていた問題を修正しました。
//	  詳細は、RRS_new_v()内のコメントを参照して下さい。
//
using System;
using System.Threading;
namespace org.piece_me {
	public static partial class libclip {
		//*****************************************************************************
		//	基本的な関数
		//*****************************************************************************
		public class ST_RRS {
			public ushort			n_job;		//ジョブの数			   =RRS_new_*()に指定した関数の数+1/*RRS_new_*()を呼び出したコンテキストのジョブ
			public ushort			i_job;		//現在実行中のジョブの順番	 0 =RRS_new_*()に指定した1番目の関数のジョブ      , 1 =RRS_new_*()に指定した2番目の関数のジョブ      ,…, n_job-1 = RRS_new_*()を呼び出したコンテキストのジョブ
			public Thread[/*(n_job-1)*/]	TBL_thr;	//各ジョブに対応するスレッド	[0]=RRS_new_*()に指定した1番目の関数のジョブに対応,[1]=RRS_new_*()に指定した2番目の関数のジョブに対応,…,[n_job-1]= RRS_new_*()を呼び出したコンテキストのジョブに対応
		}	//		~~~~~~~~~C#版特有の要素数																			 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~C#版ではこの要素は使いません。Monitor.Wait()による条件変数の動作を利用し、個々のスレッドに対するセマフォは作成しない事にしたからです。Pthreads版もこのように実装する事も可能では有ります。(2017/03/06現在のPthreads版は個々のスレッドに対するセマフォを作成していますが)
		//-----------------------------------------------------------------------------
		public static void RRS_new_l(out ST_RRS pRRS, int stacksize/*0可*/, params Action<ST_RRS>[] aFn/*[fn1,[fn2,[...,fnN]]]*/) {	//C言語版と違ってC#版では終端のnullは不要です。
			RRS_new_v(out pRRS, stacksize, aFn);
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		public static void RRS_new_v(out ST_RRS pRRS, int stacksize/*0可*/, Action<ST_RRS>[] aFn/*[fn1,[fn2,[...,fnN]]]*/) {	//C言語版と違ってC#版では終端のnullは不要です。
			//構造体のメモリを確保する。
			pRRS = new ST_RRS();
			pRRS.TBL_thr = new Thread[aFn.Length];
			//ジョブの数を格納する。
			pRRS.n_job = (ushort)(aFn.Length + 1/*自分*/);
			//自分以外の各ジョブについて…
			for(int i = 0; i < aFn.Length; i++) {
				//スレッドを作成する。
				pRRS.TBL_thr[i] = new Thread(
					delegate(object obj) {
//{{C#版特有の処理:RRS_free()対応
						try {
//}}C#版特有の処理:RRS_free()対応
							//【NOTE】
							// - スレッド内でRRS_new_v()のスタックフレーム上のaFn,及び,iにアクセスしており、それらは変化する可能性が有り一見危険に見えますが、
							//   実際には、この下でスレッドが初回のyieldを行うまで待っている(=aFn[i]が呼び出された=既にaFn[i]が読み出されている)ので安全です。
							// - 変数aFn,iだけでなく、変数pRRSもキャプチャするようにすれば、パラメタ無しのThread.Start()を使えると思ったのですが、実際に試してみた所、
							//   「error CS1628: refまたはoutパラメーター'pRRS'は、匿名メソッド、ラムダ式、またはクエリ式の内部では使用できません。」というエラーになりました。
							//   従って、pRRSだけはThread.Start()のパラメタとして引き渡すようにしました。
							aFn[i].Invoke((ST_RRS)obj);
//{{C#版特有の処理:RRS_free()対応
						} catch(ThreadInterruptedException) {
							//RRS_free()の中でThread.Interrupt()が呼ばれると、ThreadInterruptedExceptionが投入されます。
							//ThreadInterruptedExceptionを捕捉しないとエラー停止してしまうので、キャッチして無視します。
							/** no job **/
//{{2017/03/08追加:Unity環境においてサブスレッドで例外が発生して停止すると、RRSが回らなくなりUnityエディタがハングアップしていた問題を修正しました。
#if     UNITY_5_3_OR_NEWER
						} catch(Exception e) {
							// * Wed Mar 08 22:12:10 JST 2017 Naoyuki Sawa
							// - Unity環境においてサブスレッドで例外が発生して停止すると、RRSが回らなくなりUnityエディタがハングアップしていた問題を修正しました。
							//   詳細は以下の通りです。
							// - Unityエディタは、アプリケーションが生成したサブスレッドについて関知せず、サブスレッドが例外で停止しても実行を停止しないようです。
							//   そのためRRSで生成したサブスレッドで例外が発生して停止すると、メインスレッドのRRSが回らなくなりUnityエディタがハングアップします。
							//   VS2015ならば、サブスレッドで例外が発生した場合も停止してくれるのですが、Unityエディタはそのような仕組みにはなっていないようです。
							// - 上記の問題を回避するために、サブスレッドで例外が発生した時は、LogException()でログを記録した後、RRSを空回しして待つ事にしました。
							//   LogException()によってUnityエディタがエラーが発生した事を認識し、次にメインスレッドにRRSが回ってきた時に実行を中断します。(ConsoleビューでError PauseをOnにしておいて下さい。)
							// - ただし上記はあくまでも例外が発生した場合であり、Unity内部のアサート等で例外を投入せずに止まった場合はやはりUnityエディタがハングアップしてしまいます。
							//   しかし元々、UnityのサブスレッドではDebug.Log～()以外のUnity APIは使えないので、Unity内部のアサート等で例外を投入せずに止まるケースは少ないと思います。
							//   従って、大抵の場合は今回の対応で問題無いと思います。
							// ↓追記
							// - 上で「Unity内部のアサート等で例外を投入せずに止まった場合はやはりUnityエディタがハングアップしてしまいます。」と書きましたが、ハングアップせずに上手く止まる事も有るようです。
							//   例えば、サブスレッドから'UnityEngine.Camera.main.backgroundColor'を読み出す事は出来ないのでエラーになるのですが、その時Unityは例外を投入するようです。
							//   ＞ArgumentException: get_main can only be called from the main thread.
							//   とは言え、常にハングアップせずに上手く止まるかどうかは判らないので、あまりアテにはしない方が良いと思います。
							// ↓追記
							// - サブスレッドを「Debug.Assert(false);」で停止した場合も、今回の対応で上手く止まりました。
							//   Editor.logを見る限りでは例外は投入されていないのですが、内部的にはAssertでも例外が投入されているのでしょうか…?
							//   ともかく、今回の対応は、期待以上に効果的であるようです。
							UnityEngine.Debug.LogException(e);
							for(;;) { RRS_yield((ST_RRS)obj); }
#endif//UNITY_5_3_OR_NEWER
//}}2017/03/08追加:Unity環境においてサブスレッドで例外が発生して停止すると、RRSが回らなくなりUnityエディタがハングアップしていた問題を修正しました。
						}
//}}C#版特有の処理:RRS_free()対応
#if     !UNITY_5_3_OR_NEWER
					}, stacksize);
#else //!UNITY_5_3_OR_NEWER		//↓Unity対応
					// * Tue Mar 07 22:38:22 JST 2017 Naoyuki Sawa
					// - UnityのMono環境で、System.Threading.Thread()の引数maxStackSizeに0を渡すと、以下のエラーが発生するようです。(Unity Version 5.5.2f1にて確認)
					//   │ArgumentException: < 128 kb
					//   │Parameter name: maxStackSize
					//   │  at System.Threading.Thread..ctor (System.Threading.ParameterizedThreadStart start, Int32 maxStackSize) [0x00030] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Threading/Thread.cs:1002 
					//   本来、System.Threading.Thread()の引数maxStackSizeに0を渡せば、行可能ファイルのヘッダーで指定された既定の最大スタックサイズが使用されるはずなのですが、UnityのMono環境の不具合でしょうか?
					//   仕方が無いので、Unityでは、RRS_new_v()の引数stacksizeを無視して、maxStackSizeを指定しないバージョンのSystem.Threading.Thread()を呼び出す事にしました。
					//   厳密には、RRS_new_v()の引数stacksizeに0が指定された場合だけ上記の対応を行えば良いのですが、実際の所、RRS_new_v()の引数stacksizeに0以外を渡す事は無いと思うので常に無視する事にしました。
					// - UnityのMono環境かどうかを判定するために、厳密にはもっと詳細なシンボル検査を行うべきなのですが、簡単のために、UNITY_5_3_OR_NEWERだけを確認する事にしました。
					//   現在の所、'Unityかどうか?'を判定するためのシンボルとしては、UNITY_5_3_OR_NEWERを参照するのが一番簡単みたいです。(バージョン番号が入っているのが紛らわしいですが…)
					//   また、UnityはUnityでもMono環境かどうかを判断するには'ENABLE_MONO'も検査すべきなのですが、実際の所、常にMono環境だと思うので検査しなくても充分だと思います。
					}/*, stacksize*/);
#endif//!UNITY_5_3_OR_NEWER
				pRRS.TBL_thr[i].Start(pRRS);
				//このスレッドが初回のyieldを行うまで待つ。
				lock(pRRS) {
					while(pRRS.i_job == i) { Monitor.Wait(pRRS); }
					if(pRRS.i_job != (i + 1)) { throw new ApplicationException(); }	//バグ
				}
			}
			//ここで自分の順番になっています。
		}
		//-----------------------------------------------------------------------------
//{{C#版特有の処理:RRS_free()対応
		//C言語版の各プラットフォーム用の実装ではRRSを終了する事を想定していませんでしたが、C#版ではRRSを終了する方法としてRRS_free()関数を追加しました。
		//主にUnityのためです。Unityエディタ上ではアプリケーションが独自に起動したスレッドが残ったまま実行を終了するとハングアップしてしまうようなので…
		//↓訂正
		//「Unityエディタ上ではアプリケーションが独自に起動したスレッドが残ったまま実行を終了するとハングアップしてしまう」と思っていたのは間違いでした。
		//その後何度か試した所、Unityエディタ上でも,ビルドしたアプリケーションでも,Android実機でも、スレッドが残ったまま終了しても問題無く終了しました。
		//スレッドが残ったままだと終了しないのは、むしろ、VS2015で普通のWindowsアプリケーションとしてビルドした場合でした。(Environment.Exit()を使えばok)
		//とは言えUnityでもスレッドを残さない方が安心ではあるので、GameControllerオブジェクトのOnDestroy()で「RRS_free(pRRS);」を行うのが良いと思います。
		public static void RRS_free(ST_RRS pRRS) {
			//RRS_free()を呼び出せるのは、RRS_new_l(),又は,RRS_new_v()を呼び出したメインスレッドだけです。
			//RRS_new_l(),又は,RRS_new_v()で起動されたサブスレッドが、RRS_free()を呼び出してはいけません。
			//(厳密には、RRS_new_l(),又は,RRS_new_v()で起動されたサブスレッド以外であれば、別の方法で起動されたメインスレッド以外のスレッドからでもRRS_free()を呼び出す事は可能ではあります。)
			if(Array.IndexOf(pRRS.TBL_thr, Thread.CurrentThread) != -1) { throw new ApplicationException(); }
			//各サブスレッドについて…
			foreach(Thread i in pRRS.TBL_thr) {
				//スレッドを中断する。
				i.Interrupt();
				//スレッドが終了するのを待つ。
				i.Join();
			}
		}
//}}C#版特有の処理:RRS_free()対応
		//-----------------------------------------------------------------------------
		public static void RRS_yield(ST_RRS pRRS) {
			//自分のジョブの順番を取得する。
			int i_job = pRRS.i_job;
			lock(pRRS) {
				//順番を次へ進める。
				if(++pRRS.i_job == pRRS.n_job) { pRRS.i_job = 0; }
				Monitor.PulseAll(pRRS);		//Monitor.Pulse()ではダメ(だと思う…)。
				//自分の順番が来るまで待つ。
				while(pRRS.i_job != i_job) {	//(pRRS.n_job==1)である可能性が有るのでdo～whileではダメ。
					Monitor.Wait(pRRS);
				}
			}
		}
		//-----------------------------------------------------------------------------
		public static int RRS_n_job(ST_RRS pRRS) {
			//ジョブの数を返す。
			return pRRS.n_job;
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		public static int RRS_i_job(ST_RRS pRRS) {
			//現在実行中のジョブの順番を返す。
			return pRRS.i_job;
		}
		//*****************************************************************************
		//	ジョブ同期用の関数
		//*****************************************************************************
		public class ST_RRS_sync_root {
			public ushort			req;		//同期させるジョブの数			1以上の値で初期化しておく事。当モジュールの関数はこのフィールドを変更しない。
			public ushort			cnt;		//到着したジョブの数			初期値は0とする事。RRS_sync()が呼び出される度に0→reqまで増加して行き、バリアが解除されたら0に戻る。		ノードチェインの長さと同じなので保持しなくても良いのだが、何度もノードチェインの長さを数える速度低下を避けるために、このフィールドに保持しておく事にした。
			public ST_RRS_sync_node		first;		//到着したジョブのノードチェイン	初期値はnullとする事。各RRS_sync()呼び出しのローカル変数nodeをここにチェインして行き、バリアが解除されたらnullに戻る。
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		public class ST_RRS_sync_node {
			public bool			done;		//バリアが解除された事を示すフラグ
			public ST_RRS_sync_node		next;		//ノードチェインの次のノード
		}
		//-----------------------------------------------------------------------------
		public static void RRS_sync(ST_RRS pRRS, ST_RRS_sync_root root) {
			RRS_sync_idle(pRRS, root, null, null);
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		public static void RRS_sync_idle(ST_RRS pRRS, ST_RRS_sync_root root, Action<object/*arg*/> idle, object arg) {
			ST_RRS_sync_node node = new ST_RRS_sync_node();
			//既に、到着したジョブの数が、同期させるジョブの数以上になっていたら、エラーです。
			// - もしここで停止したら、アプリケーションがroot.reqの初期化を忘れて、(root.req==0)である可能性が有ります。
			if(root.cnt >= root.req) { throw new ApplicationException(); }
			//ローカル変数nodeを、到着したジョブのノードチェインのチェインする。
			// - チェインの順序は結果に影響しないので、先頭にチェインしても末尾にチェインしても構いません。
			//   先頭にチェインする方が効率が良いので、現在の実装では、先頭にチェインするようにしています。
		//不要	node.done = false;	//忘れないで!!
			node.next = root.first;
			            root.first = node;
			//到着したジョブの数を増やして、同期させるジョブの数に到達したら…
			if(++root.cnt == root.req) {
				//到着したジョブのノードチェインにチェインされている、全てのノードに、バリアが解除された事を示すフラグをセットする。
				do {
#if     DEBUG
					//デバッグビルドならば、ノードチェインの長さと到着したジョブの数が一致している事を確認するために、到着したジョブの数を減らして行く。
					// - このループを抜けた所で、到着したジョブの数が0になったかを確認します。
					root.cnt--;
					//デバッグビルドならば、バリアが解除された事を示すフラグがまだセットされていない事を確認する。
					// - もしここで停止したら、当モジュールのバグです。
					if(root.first.done) { throw new ApplicationException(); }
#endif//DEBUG
					root.first.done = true;
					root.first = root.first.next;
				} while(root.first != null);
#if     DEBUG
				//デバッグビルドならば、ノードチェインの長さと到着したジョブの数が一致している事を確認する。
				// - もしここで停止したら、当モジュールのバグか,又は,別のジョブが誤って使用中のrootを再初期化した可能性が有ります。	⇒{{2016/06/16コメント追記:左記のバグは、'Thu Jun 16 20:40:56 JST 2016'に追加した「'バリアが解除された事を示すフラグがセットされるまでループして待つ'処理の中で、ノードチェインが破壊されていないかを検査するデバッグ処理」の方で先に検出されると思いますが、より安全のためにここでの検査も残しておく事にしました。}}
				//   原則としては、rootをグローバル変数として定義し、reqフィールドを静的に初期化しておけば、アプリケーションがrootを明示的に再初期化する処理は必要無いはずです。
				if(root.cnt != 0) { throw new ApplicationException(); }
#endif//DEBUG
				root.cnt = 0;	//デバッグビルドでは上で既に(root.cnt=0)である事を確認済みなので、この処理はダミー処理となります。
			}
			//～～～～～～～～～～■■重要!!■■当関数内のここより下の処理では、rootを参照してはいけません。先行してバリア解除されたジョブが、同じrootを使って次の同期を開始している可能性が有るからです。～～～～～～～～～～
			//バリアが解除された事を示すフラグがセットされるまでループして待つ。
			// - 今回のRRS_sync()呼び出しによって到着したジョブの数が同期させるジョブの数に到達した場合は、上の処理で既にバリアが解除された事を示すフラグがセットされており、下記のループは一度も実行しません。
			while(!node.done) {
#if     DEBUG
				// * Thu Jun 16 20:40:56 JST 2016 Naoyuki Sawa
				// - 'バリアが解除された事を示すフラグがセットされるまでループして待つ'処理の中で、ノードチェインが破壊されていないかを検査するデバッグ処理を追加しました。
				//   詳細は、以下の通りです。
				// - 上で、「■■重要!!■■当関数内のここより下の処理では、rootを参照してはいけません。」と書いたのですが、厳密には、ここでrootを参照する事は可能です。
				//   なぜなら、'while(!node.done)'がFalseと判定された直後なので、まだこのrootの今回のバリア解除が行われていない事が確実だからです。
				//   従って、ここで、rootからノードチェインをたどって検査を行う事は可能です。
				//   (毎ループの、'while(!node.done)'がFalseと判定された直後，'RRS_yield(pRRS)'を呼ぶ前に行う必要が有る事に注意して下さい!!'RRS_yield(pRRS)'を呼ぶと、その中でバリア解除が行われる可能性が有るからです。)
				// - 検査する点としては、ノードチェインに自分のノードが含まれているかを確認する事にしました。
				//   何らかのバグで、ノードチェインから自分のノードが外れると、バリア解除が行われた時に自分のノードのフラグがセットされず、永久に待ち続けるという検出しづらい症状になるので、その前に発見したいからです。
				//   ノードチェインから自分のノードが外れるバグが生じるケースとしては、使用中のrootを他のジョブが再初期化した場合などです。
				//   <例>
				//   │ST_RRS_sync_root r = {2};
				//   │void job1(ST_RRS pRRS) { RRS_sync(pRRS, r); ～ }
				//   │void job2(ST_RRS pRRS) { r = (ST_RRS_sync_root){2}; }	←job1がrでバリア解除待ちしている途中に、rを再初期化しているバグです。この後、job1のRRS_sync()のループ処理の中で、バグ検出されるはずです。
				ST_RRS_sync_node pnode;
				for(pnode = root.first; pnode != null; pnode = pnode.next) {	//ノードチェインをたどって…
					if(pnode == node) { break; }				//自分のノードが含まれていたら抜ける。
				}
				if(pnode == null) { throw new ApplicationException(); }		//自分のノードが含まれていなかったら、エラー停止する。
#endif//DEBUG
				//アプリケーション定義のアイドリング関数が指定されていたら、アイドリング関数を呼び出す。
				// - アプリケーション定義のアイドリング関数の中では、RRS_yield()を呼び出さないように注意して下さい。(要するに、アイドリング関数としてschedule()を指定してはいけないと言う事です。)	┐
				//   もし、アプリケーション定義のアイドリング関数の中でRRS_yield()が呼び出されると、この下で行うRRS_yield()呼び出しと合わせて、二回RRS_yield()が行われてしまいます。			├【重要】
				//   必ずしも致命的な問題になるとは限らないのですが、このジョブの性能が低下したり、アプリケーションが特定の同期タイミングを想定している場合は致命的な問題になる可能性も有ります。	┘
				// - 即、同期完了した場合は、このブロックを通らないので、アイドリング関数を一度も呼び出しません。
				//   そうしないと、RRS_sync_idle()を呼び出す度に必ずアイドリング関数が一回実行されて、性能が低下してしまう問題が発生するからです。
				idle?.Invoke(arg);	//「if(idle != null) { idle.Invoke(arg); }」と書いても同じです。
				//スケジューリングを実行する。
				RRS_yield(pRRS);
			}
		}
	}
}
//*****************************************************************************
//	ジョブ同期用の関数の使用例
//*****************************************************************************
#if false
// * Mon Mar 06 23:24:18 JST 2017 Naoyuki Sawa
// - cliprrs.cでC言語版に対して行ったのと同じ検証を、C#版でも行ったので記録しておきます。
//   結果、C言語版と同じ結果になったので、C#版も正しく動作していると思います。
//□テストプログラム
	#define TEST_SYNC	//このシンボルを定義しなければ、同期無しでテスト実行します。このシンボルを定義すると、同期有りでテスト実行します。
using System;
using static org.piece_me.libclip;
class Program {
	static ST_RRS	pRRS;
	static void Main() {
		RRS_new_l(out pRRS, 0/*stacksize*/,
			job1,
			job2,
			job3,
			job4);
		while(!Console.KeyAvailable) { RRS_yield(pRRS); }
		RRS_free(pRRS);
	}
#if     TEST_SYNC
	static ST_RRS_sync_root sr3 = new ST_RRS_sync_root() { req = 3 };	//3ジョブ同期用	┐この例では、同期するジョブ数毎にrootを設けましたが、必ずしも、同期するジョブ数毎に一つのrootを設ける訳では有りません。同期する組毎に、一組のrootを設けて下さい。
	static ST_RRS_sync_root sr4 = new ST_RRS_sync_root() { req = 4 };	//4ジョブ同期用	┘例えば、全体で4ジョブが有り、2ジョブ×2毎の組で同期を取る場合は、2ジョブ同期用のrootを二組設けて下さい。ただし、二組の同期が同時に発生しない場合は、全体で一組のrootを共用しても構いません。
#endif//TEST_SYNC
	static void job1(ST_RRS pRRS) {
		int i;
		Console.WriteLine("job1 start");
		//5サイクルかかる処理を実行する想定
		for(i = 0; i < 5; i++) { RRS_yield(pRRS); }
		Console.WriteLine("job1 calcA done");
#if     TEST_SYNC
		//3ジョブ同期
		RRS_sync(pRRS, sr3);
		Console.WriteLine("job1 syncA done");
#endif//TEST_SYNC
		//9サイクルかかる処理を実行する想定
		for(i = 0; i < 9; i++) { RRS_yield(pRRS); }
		Console.WriteLine("job1 calcB done");
#if     TEST_SYNC
		//3ジョブ同期
		RRS_sync(pRRS, sr3);
		Console.WriteLine("job1 syncB done");
		//4ジョブ同期
		RRS_sync(pRRS, sr4);
#endif//TEST_SYNC
		Console.WriteLine("job1 end");
		for(;;) { RRS_yield(pRRS); }
	}
	static void job2(ST_RRS pRRS) {
		int i;
		Console.WriteLine("job2 start");
		//7サイクルかかる処理を実行する想定
		for(i = 0; i < 7; i++) { RRS_yield(pRRS); }
		Console.WriteLine("job2 calcA done");
#if     TEST_SYNC
		//3ジョブ同期
		RRS_sync(pRRS, sr3);
		Console.WriteLine("job2 syncA done");
#endif//TEST_SYNC
		//3サイクルかかる処理を実行する想定
		for(i = 0; i < 3; i++) { RRS_yield(pRRS); }
		Console.WriteLine("job2 calcB done");
#if     TEST_SYNC
		//3ジョブ同期
		RRS_sync(pRRS, sr3);
		Console.WriteLine("job2 syncB done");
		//4ジョブ同期
		RRS_sync(pRRS, sr4);
#endif//TEST_SYNC
		Console.WriteLine("job2 end");
		for(;;) { RRS_yield(pRRS); }
	}
	static void job3(ST_RRS pRRS) {
		Console.WriteLine("job3 start");
#if     TEST_SYNC
		//3ジョブ同期
		RRS_sync(pRRS, sr3);
		Console.WriteLine("job3 syncA done");
		//3ジョブ同期
		RRS_sync(pRRS, sr3);
		Console.WriteLine("job3 syncB done");
		//4ジョブ同期
		RRS_sync(pRRS, sr4);
#endif//TEST_SYNC
		Console.WriteLine("job3 end");
		for(;;) { RRS_yield(pRRS); }
	}
	static void job4(ST_RRS pRRS) {
		Console.WriteLine("job4 start");
#if     TEST_SYNC
		//4ジョブ同期
		RRS_sync(pRRS, sr4);
#endif//TEST_SYNC
		Console.WriteLine("job4 end");
		for(;;) { RRS_yield(pRRS); }
	}
}
//──────────────────────────────────────
//□同期無しでテスト実行した結果(TEST_SYNC定義無し)
job1 start
job2 start
job3 start
job3 end
job4 start
job4 end
job1 calcA done
job2 calcA done
job2 calcB done
job2 end
job1 calcB done
job1 end
//□同期有りでテスト実行した結果(TEST_SYNC定義有り)
job1 start
job2 start
job3 start
job4 start
job1 calcA done
job2 calcA done
job2 syncA done
job3 syncA done
job1 syncA done
job2 calcB done
job1 calcB done
job1 syncB done
job2 syncB done
job3 syncB done
job3 end
job4 end
job1 end
job2 end
#endif
