//
//	clipmsgh.cs
//
//	メッセージハンドシェイク
//
//	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の場合はデバッグ出力にUnityEngine.Debug.LogFormat()を使うように変更しました。
//	  UnityでSystem.Diagnostics.Debug.WriteLine()を使うと、以下のエラーが出たからです。(Unity Version 5.5.2f1にて確認)
//	  ＞MissingMethodException: Method not found: 'System.Diagnostics.Debug.WriteLine'.
//
using System;
namespace org.piece_me {
	public static partial class libclip {
		//*****************************************************************************
		//	構造体
		//*****************************************************************************
		public class ST_MsgHsk {
			public ST_RRS		pRRS;		//
			public short		iJobSend;	//送信完了待ち中のジョブ番号(0～)。(-1)ならば送信完了待ち中のジョブが無い事を示す。
			public short		iJobRecv;	//受信完了待ち中のジョブ番号(0～)。(-1)ならば受信完了待ち中のジョブが無い事を示す。
			public int		iMsgType;	//送信中のメッセージタイプ(-1以外)		┬(iJobSend!=-1)時のみ有効
			public object		pMsgData;	//送信中のメッセージデータポインタ(任意)	┘
		}
		//*****************************************************************************
		//	
		//*****************************************************************************
		public static void MsgHsk_Init(ST_MsgHsk pMsgHsk, ST_RRS pRRS) {
			pMsgHsk.pRRS     = pRRS;
			pMsgHsk.iJobRecv = -1;
			pMsgHsk.iJobSend = -1;
		//不要	pMsgHsk.iMsgType = -1;		//不定値で構わない。
		//不要	pMsgHsk.pMsgData = null;	//不定値で構わない。
		}
		//-----------------------------------------------------------------------------
		//メッセージ{iMsgType,pMsgData}を送信する。
		//相手のジョブがメッセージを受信するまで、当関数は処理を返さない。
		//当関数が処理を返したら、相手のジョブがpMsgDataの内容を参照済みであるから、pMsgDataの内容を破棄して構わない。
		public static void MsgHsk_Send(ST_MsgHsk pMsgHsk, int iMsgType/*(-1)以外*/, object pMsgData/*null可*/) {
			//MsgHsk_Send()は、アイドリング関数を指定せずにMsgHsk_SendIdle()を呼び出す事と等価です。
			MsgHsk_SendIdle(pMsgHsk, iMsgType, pMsgData, null, null);
		}
		public static void MsgHsk_SendIdle(ST_MsgHsk pMsgHsk, int iMsgType/*(-1)以外*/, object pMsgData/*null可*/, Action<object/*arg*/> idle/*null可*/, object arg) {
			//当関数を呼び出したジョブの、ジョブ番号(0～)を取得しておく。
			int iJob = RRS_i_job(pMsgHsk.pRRS);
#if     DEBUG
#if     !UNITY_5_3_OR_NEWER
			System.Diagnostics.Debug.WriteLine("MsgHsk_Send: Job#{0} is sending Msg#{1}.", iJob, iMsgType);
#else //!UNITY_5_3_OR_NEWER
			UnityEngine.Debug.LogFormat(       "MsgHsk_Send: Job#{0} is sending Msg#{1}.", iJob, iMsgType);
#endif//!UNITY_5_3_OR_NEWER
#endif//DEBUG
			//メッセージタイプとして、(-1)は使用不可です。
			//MsgHsk_Recv()の戻り値として、メッセージが無かった事を示す戻り値(-1)と、区別できなくなるからです。
			if(iMsgType == -1) { throw new ApplicationException(); }
			//送信⇔送信デッドロックならば、エラー停止する。
			if(pMsgHsk.iJobSend != -1) { throw new ApplicationException(); }
			//送信情報を格納する。
			pMsgHsk.iJobSend = (short)iJob;	//送信完了待ち中のジョブ番号(0～)
			pMsgHsk.iMsgType = iMsgType;	//メッセージ番号((-1)以外)
			pMsgHsk.pMsgData = pMsgData;	//メッセージデータポインタ(任意)
			//送信完了を待つ。
			for(;;) {
				RRS_yield(pMsgHsk.pRRS);
			//誤	if(pMsgHsk.iJobSend == -1) { break; }		//これは誤り。相手のジョブがMsgHsk_Recv()を呼び出して(pMsgHsk.iJobSend=-1)になった後、相手のジョブがRRS_yield()を呼び出す前にMsgHsk_Send()を呼び出した場合、ここで既に(pMsgHsk.iJobSend=相手のジョブ番号)になっている可能性が有るから。
			/*正*/	if(pMsgHsk.iJobSend != iJob) { break; }	//こうすれば、(pMsgHsk.iJobSend=-1)になっていても、(pMsgHsk.iJobSend=相手のジョブ番号)になっていても、正しく判定する事が出来る。
				//アプリケーション定義のアイドリング関数が指定されていたら、アイドリング関数を呼び出す。
				// - アプリケーション定義のアイドリング関数の中では、RRS_yield()を呼び出さないように注意して下さい。(要するに、アイドリング関数としてschedule()を指定してはいけないと言う事です。)	┐
				//   もし、アプリケーション定義のアイドリング関数の中でRRS_yield()が呼び出されると、この上で行ったRRS_yield()呼び出しと合わせて、二回RRS_yield()が行われてしまいます。			├【重要】
				//   必ずしも致命的な問題になるとは限らないのですが、このジョブの性能が低下したり、アプリケーションが特定の同期タイミングを想定している場合は致命的な問題になる可能性も有ります。	┘
				// - 送信情報を格納した直後のRRS_yield()で相手ジョブが受信を完了した場合は(要するに、こちらが送信を行う前に相手が受信待ち状態だったか,又は,送信した直後に相手が受信を行ったら)、アイドリング関数を一度も呼び出しません。
				//   そうしないと、MsgHsk_SendIdle()を呼び出す度に必ずアイドリング関数が一回実行されて、性能が低下してしまう問題が発生するからです。
				//   この対応を行うために、変更前はdo{～}whileループだったのを、for{～break～}ループに変更しました。
				idle?.Invoke(arg);	//「if(idle != null) { idle.Invoke(arg); }」と書いても同じです。
			}
			//この時点で、MsgHsk_Recv()を呼び出したジョブからの、メッセージデータへのアクセスは終了している。
			//MsgHsk_Send()を呼び出したジョブは、当関数が処理を返したら、メッセージデータを破壊して構わない。
#if     DEBUG
#if     !UNITY_5_3_OR_NEWER
			System.Diagnostics.Debug.WriteLine("MsgHsk_Send: Job#{0} has sent Msg#{1}.", iJob, iMsgType);
#else //!UNITY_5_3_OR_NEWER
			UnityEngine.Debug.LogFormat(       "MsgHsk_Send: Job#{0} has sent Msg#{1}.", iJob, iMsgType);
#endif//!UNITY_5_3_OR_NEWER
#endif//DEBUG
		}
		//-----------------------------------------------------------------------------
		//ブロッキングモードで、メッセージを受信する。
		//メッセージが無ければ、メッセージが到着するまで待つ。
		//当関数が(-1)を返す事は無い。
		//(*ppMsgData)を取得した場合、次にRRS_yield()を呼び出した時点で、その内容は相手のジョブによって破棄される可能性が有る事に注意せよ。
		public static int MsgHsk_Recv(ST_MsgHsk pMsgHsk) {
			object ppMsgData;
			return MsgHsk_Recv(pMsgHsk, out ppMsgData);
		}
		public static int MsgHsk_Recv(ST_MsgHsk pMsgHsk, out object ppMsgData) {
			//MsgHsk_Recv()は、アイドリング関数を指定せずにMsgHsk_RecvIdle()を呼び出す事と等価です。
			return MsgHsk_RecvIdle(pMsgHsk, out ppMsgData, null, null);
		}
		public static int MsgHsk_RecvIdle(ST_MsgHsk pMsgHsk, Action<object/*arg*/> idle/*null可*/, object arg) {
			object ppMsgData;
			return MsgHsk_RecvIdle(pMsgHsk, out ppMsgData, idle/*null可*/, arg);
		}
		public static int MsgHsk_RecvIdle(ST_MsgHsk pMsgHsk, out object ppMsgData, Action<object/*arg*/> idle/*null可*/, object arg) {
			//当関数を呼び出したジョブの、ジョブ番号(0～)を取得しておく。
			int iJob = RRS_i_job(pMsgHsk.pRRS);
			//送信情報が無ければ…
			if(pMsgHsk.iJobSend == -1) {
#if     DEBUG
#if     !UNITY_5_3_OR_NEWER
				System.Diagnostics.Debug.WriteLine("MsgHsk_Recv: Job#{0} is receiving Msg.", iJob);
#else //!UNITY_5_3_OR_NEWER
				UnityEngine.Debug.LogFormat(       "MsgHsk_Recv: Job#{0} is receiving Msg.", iJob);
#endif//!UNITY_5_3_OR_NEWER
#endif//DEBUG
				//受信⇔受信デッドロックならば、エラー停止する。
				if(pMsgHsk.iJobRecv != -1) { throw new ApplicationException(); }
				//受信情報を格納する。
				pMsgHsk.iJobRecv = (short)iJob;
				//送信情報を待つ。
				do {
					//アプリケーション定義のアイドリング関数が指定されていたら、アイドリング関数を呼び出す。
					// - アプリケーション定義のアイドリング関数の中では、RRS_yield()を呼び出さないように注意して下さい。(要するに、アイドリング関数としてschedule()を指定してはいけないと言う事です。)	┐
					//   もし、アプリケーション定義のアイドリング関数の中でRRS_yield()が呼び出されると、この下で行うRRS_yield()呼び出しと合わせて、二回RRS_yield()が行われてしまいます。			├【重要】
					//   必ずしも致命的な問題になるとは限らないのですが、このジョブの性能が低下したり、アプリケーションが特定の同期タイミングを想定している場合は致命的な問題になる可能性も有ります。	┘
					// - 既に送信情報が有った場合は、このブロックを通らないので、アイドリング関数を一度も呼び出しません。
					//   そうしないと、MsgHsk_RecvIdle()を呼び出す度に必ずアイドリング関数が一回実行されて、性能が低下してしまう問題が発生するからです。
					idle?.Invoke(arg);	//「if(idle != null) { idle.Invoke(arg); }」と書いても同じです。
					RRS_yield(pMsgHsk.pRRS);
				} while(pMsgHsk.iJobSend == -1);
				//受信情報をクリアする。
				pMsgHsk.iJobRecv = -1;
			}
#if     DEBUG
#if     !UNITY_5_3_OR_NEWER
			System.Diagnostics.Debug.WriteLine("MsgHsk_Recv: Job#{0} has received Msg#{1}.", iJob, pMsgHsk.iMsgType);
#else //!UNITY_5_3_OR_NEWER
			UnityEngine.Debug.LogFormat(       "MsgHsk_Recv: Job#{0} has received Msg#{1}.", iJob, pMsgHsk.iMsgType);
#endif//!UNITY_5_3_OR_NEWER
#endif//DEBUG
			//送信情報をクリアする。
			pMsgHsk.iJobSend = -1;
			//メッセージデータポインタを格納する変数が指定されていたら、メッセージデータポインタを格納する。
			// - MsgHsk_Recv()を呼び出したジョブは、次にRRS_yield()を呼び出すまで、メッセージデータの内容に直接アクセス出来ます。
			//   MsgHsk_Recv()を呼び出したジョブが、次にRRS_yield()を呼び出すまで、MsgHsk_Send()を呼び出したジョブの処理が進まない(=MsgHsk_Send()が処理を返さない)ので、メッセージデータの内容は変わらないからです。
			// - MsgHsk_Recv()を呼び出したジョブは、メッセージデータを参照用と見なしても構いませんし、書き込んでも構いません。
			//   MsgHsk_Recv()を呼び出したジョブと、MsgHsk_Send()を呼び出したジョブの、認識が一致していれば、メッセージデータは単なるバッファであり、任意に読み書きする事が出来ます。
			ppMsgData = pMsgHsk.pMsgData;
			//メッセージ番号を返す。
			if(pMsgHsk.iMsgType == -1) { throw new ApplicationException(); }	//MsgHsk_Send()で検査しているので、メッセージ番号として(-1)が格納される事は無いはず。もしここで停止したら当モジュールのバグです。
			return pMsgHsk.iMsgType;
		}
		//-----------------------------------------------------------------------------
		//非ブロッキングモードで、メッセージを受信する。
		//メッセージが無ければ、メッセージが無かった事を示す戻り値(-1)を返す。
		//メッセージが有れば、MsgHsk_Recv()と同じ動作を行う。
		public static int MsgHsk_TryRecv(ST_MsgHsk pMsgHsk) {
			object ppMsgData;
			return MsgHsk_TryRecv(pMsgHsk, out ppMsgData);
		}
		public static int MsgHsk_TryRecv(ST_MsgHsk pMsgHsk, out object ppMsgData) {
			ppMsgData = null;	//エラー抑制
			//送信情報が無ければ、メッセージが無かった事を示す戻り値(-1)を返す。
			if(pMsgHsk.iJobSend == -1) { return -1; }
			//ブロッキングモードで、メッセージを受信する。
			//送信情報が有る事を確認済みであるから、ブロッキングされる事は無い。
			return MsgHsk_Recv(pMsgHsk, out ppMsgData);
		}
		//-----------------------------------------------------------------------------
		//ブロッキングモードで、メッセージを受信する。
		//受信したメッセージのメッセージタイプが、指定されたメッセージタイプに一致する事を確認する。
		//もし違っていたら、当関数内でエラー停止する。
		//それ以外は、MsgHsk_Recv()と同じです。
		public static void MsgHsk_Wait(ST_MsgHsk pMsgHsk, int iMsgType/*(-1)以外*/) {
			object ppMsgData;
			MsgHsk_Wait(pMsgHsk, iMsgType/*(-1)以外*/, out ppMsgData);
		}
		public static void MsgHsk_Wait(ST_MsgHsk pMsgHsk, int iMsgType/*(-1)以外*/, out object ppMsgData) {
			//MsgHsk_Wait()は、アイドリング関数を指定せずにMsgHsk_WaitIdle()を呼び出す事と等価です。
			MsgHsk_WaitIdle(pMsgHsk, iMsgType, out ppMsgData, null, null);
		}
		public static void MsgHsk_WaitIdle(ST_MsgHsk pMsgHsk, int iMsgType/*(-1)以外*/, Action<object/*arg*/> idle/*null可*/, object arg) {
			object ppMsgData;
			MsgHsk_WaitIdle(pMsgHsk, iMsgType/*(-1)以外*/, out ppMsgData, idle/*null可*/, arg);
		}
		public static void MsgHsk_WaitIdle(ST_MsgHsk pMsgHsk, int iMsgType/*(-1)以外*/, out object ppMsgData, Action<object/*arg*/> idle/*null可*/, object arg) {
#if     DEBUG
			int iJob = RRS_i_job(pMsgHsk.pRRS);
#if     !UNITY_5_3_OR_NEWER
			System.Diagnostics.Debug.WriteLine("MsgHsk_Recv: Job#{0} is waiting for Msg#{1}.", iJob, iMsgType);
#else //!UNITY_5_3_OR_NEWER
			UnityEngine.Debug.LogFormat(       "MsgHsk_Recv: Job#{0} is waiting for Msg#{1}.", iJob, iMsgType);
#endif//!UNITY_5_3_OR_NEWER
#endif//DEBUG
			//メッセージタイプとして、(-1)は使用不可です。
			//MsgHsk_Recv()の戻り値として、メッセージが無かった事を示す戻り値(-1)と、区別できなくなるからです。
			if(iMsgType == -1) { throw new ApplicationException(); }
			//ブロッキングモードで、メッセージを受信する。
			//受信したメッセージタイプが、指定されたメッセージタイプと異なっていたら、エラー停止する。
			if(MsgHsk_RecvIdle(pMsgHsk, out ppMsgData, idle, arg) != iMsgType) { throw new ApplicationException(); }
		}
	}
}
//*****************************************************************************
//	使用例①　片方向通信の例
//*****************************************************************************
#if false
using System;
using System.Diagnostics;
using static org.piece_me.libclip;
class Program {
	static ST_RRS		pRRS;
	static ST_MsgHsk	stMsgHsk = new ST_MsgHsk();
	static Random		seed = new Random();
	static void job1_main(ST_RRS pRRS) {
		int i, iSend = 0x1000;
		object pData;
		//当関数が呼び出された時点で、まだMsgHsk_Init()は実行されていないので、一回待つ。←←←!!要注意!!
		RRS_yield(pRRS);
		for(;;) {
			//0回以上の任意回数のRRS_yield()を行う。0回でも構わない。
			for(i = seed.Next() % 10; i != 0; i--) { RRS_yield(pRRS); }
			//送信
			pData = string.Format("{0:X4}", iSend);		//メッセージデータを作成する。
			MsgHsk_Send(stMsgHsk, iSend++, pData);		//この関数が処理を返した時点で、相手ジョブのメッセージデータへのアクセスは完了している。
		}
	}
	static void job2_main(ST_RRS pRRS) {
		int i, iRecv;
		object pData;
		//当関数が呼び出された時点で、まだMsgHsk_Init()は実行されていないので、一回待つ。←←←!!要注意!!
		RRS_yield(pRRS);
		for(;;) {
			//0回以上の任意回数のRRS_yield()を行う。0回でも構わない。
			for(i = seed.Next() % 10; i != 0; i--) { RRS_yield(pRRS); }
			//受信
			iRecv = MsgHsk_Recv(stMsgHsk, out pData);	//メッセージを受信する。
			Debug.WriteLine("{0:X4}: {1}", iRecv, pData);	//次にRRS_yield()を呼び出すまでの間、メッセージデータへのアクセスが可能である。この例では受信側はメッセージデータを参照用としているが、受信側⇒送信側への受け渡しと見なして書き込んでも構わない。
		}
	}
	static void Main() {
		RRS_new_l(out pRRS, 0/*stacksize*/,
			job1_main,	//ジョブ1
			job2_main);	//ジョブ2
		MsgHsk_Init(stMsgHsk, pRRS);
		for(;;) { RRS_yield(pRRS); }
	}
}
#endif
//*****************************************************************************
//	使用例②　双方向通信の例
//*****************************************************************************
#if false
using System;
using System.Diagnostics;
using static org.piece_me.libclip;
class Program {
	static ST_RRS		pRRS;
	static ST_MsgHsk	stMsgHsk = new ST_MsgHsk();
	static Random		seed = new Random();
	static void job1_main(ST_RRS pRRS) {
		int i, iRecv, iSend = 0x1000;
		object pData;
		//当関数が呼び出された時点で、まだMsgHsk_Init()は実行されていないので、一回待つ。←←←!!要注意!!
		RRS_yield(pRRS);
		for(;;) {
			//0回以上の任意回数のRRS_yield()を行う。0回でも構わない。
			for(i = seed.Next() % 10; i != 0; i--) { RRS_yield(pRRS); }
			//送信
			pData = string.Format("{0:X4}", iSend);		//メッセージデータを作成する。
			MsgHsk_Send(stMsgHsk, iSend++, pData);		//この関数が処理を返した時点で、相手ジョブのメッセージデータへのアクセスは完了している。
			//0回以上の任意回数のRRS_yield()を行う。0回でも構わない。
			for(i = seed.Next() % 10; i != 0; i--) { RRS_yield(pRRS); }
			//受信
			iRecv = MsgHsk_Recv(stMsgHsk, out pData);	//メッセージを受信する。
			Debug.WriteLine("{0:X4}: {1}", iRecv, pData);	//次にRRS_yield()を呼び出すまでの間、メッセージデータへのアクセスが可能である。この例では受信側はメッセージデータを参照用としているが、受信側⇒送信側への受け渡しと見なして書き込んでも構わない。
		}
	}
	static void job2_main(ST_RRS pRRS) {
		int i, iRecv, iSend = 0x2000;
		object pData;
		//当関数が呼び出された時点で、まだMsgHsk_Init()は実行されていないので、一回待つ。←←←!!要注意!!
		RRS_yield(pRRS);
		for(;;) {
			//0回以上の任意回数のRRS_yield()を行う。0回でも構わない。
			for(i = seed.Next() % 10; i != 0; i--) { RRS_yield(pRRS); }
			//受信
			iRecv = MsgHsk_Recv(stMsgHsk, out pData);	//メッセージを受信する。
			Debug.WriteLine("{0:X4}: {1}", iRecv, pData);	//次にRRS_yield()を呼び出すまでの間、メッセージデータへのアクセスが可能である。この例では受信側はメッセージデータを参照用としているが、受信側⇒送信側への受け渡しと見なして書き込んでも構わない。
			//0回以上の任意回数のRRS_yield()を行う。0回でも構わない。
			for(i = seed.Next() % 10; i != 0; i--) { RRS_yield(pRRS); }
			//送信
			pData = string.Format("{0:X4}", iSend);		//メッセージデータを作成する。
			MsgHsk_Send(stMsgHsk, iSend++, pData);		//この関数が処理を返した時点で、相手ジョブのメッセージデータへのアクセスは完了している。
		}
	}
	static void Main() {
		RRS_new_l(out pRRS, 0/*stacksize*/,
			job1_main,	//ジョブ1
			job2_main);	//ジョブ2
		MsgHsk_Init(stMsgHsk, pRRS);
		for(;;) { RRS_yield(pRRS); }
	}
}
#endif
