/*
 *	clipmsgh.c
 *
 *	メッセージハンドシェイク
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2016 Naoyuki Sawa
 *
 *	* Wed Mar 16 21:18:14 JST 2016 Naoyuki Sawa
 *	- 1st リリース。
 *	- RRSによって管理されている複数のジョブ間で、メッセージ通信を行うためのモジュールです。
 *	  特徴は、ST_MsgHsk構造体の中にバッファを設けず、送信者が作成したバッファのメモリを、受信者が直接参照する事です。
 *	  要するに、共有メモリを使ったジョブ間通信です。
 *	  当モジュールの役割は、受信側がデータ参照を終えるまで送信側をブロックする事と、デッドロックの検出を行う事です。
 *	- 当モジュールは、基本的には、二つのジョブ間での通信を想定しています。
 *	  RRSの中に三個以上のジョブが有っても構いませんが、一つのST_MsgHskを使って通信するジョブは二つに固定する方が安全です。
 *	  送受信タイミングが被らないように気を付ければ、一つのST_MsgHskで三つ以上のジョブ間通信を行う事も可能ではありますが、複雑になったり再現し辛いバグの原因になるので望ましくありません。
 *	  三つ以上のジョブ間で通信を行う場合は、二個のジョブの対ごとに、個別のST_MsgHskを作成する方が判り易いと思います。
 *	* Thu Mar 17 21:54:13 JST 2016 Naoyuki Sawa
 *	- 「使用例①　片方向通信の例」「使用例②　双方向通信の例」を書きました。
 *	* Mon Jul 25 21:36:27 JST 2016 Naoyuki Sawa
 *	- MsgHsk_Recv()を、MsgHsk_TryRecv()とMsgHsk_Recv()に分離しました。
 *	  具体的には、以下の通りです。
 *	- 変更前は、MsgHsk_Recv()のbBlocking引数によって、ブロッキングモードと非ブロッキングモードを選択していました。
 *	  変更後は、MsgHsk_Recv()はブロッキングモード専用の関数、MsgHsk_TryRecv()は非ブロッキングモード専用の関数としました。
 *	  それに伴い、MsgHsk_Recv()のbBlocking引数を廃止しました。
 *	  上記の変更を行った理由は、実際のアプリケーションではブロッキングモードで使用する事がほとんどであり、毎回(bBlocking=1)を指定するのは使い辛いと思ったからです。
 *	* Fri Sep 30 21:41:38 JST 2016 Naoyuki Sawa
 *	- MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
 *	  これまで、MsgHsk_Send(),MsgHsk_Recv(),MsgHsk_Wait()内でブロックされている間は、アプリケーション定義の周期処理が呼び出せない問題が有りました。
 *	  その問題を解決するために、ブロックされている間に呼び出されるアプリケーション定義のアイドリング関数を指定出来るバージョンの関数を追加しました。
 */
#include "clip.h"
/****************************************************************************
 *	
 ****************************************************************************/
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の内容を破棄して構わない。
void MsgHsk_Send(ST_MsgHsk* pMsgHsk, int iMsgType/*(-1)以外*/, void* pMsgData/*NULL可*/) {
//{{2016/09/30追加:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
	//MsgHsk_Send()は、アイドリング関数を指定せずにMsgHsk_SendIdle()を呼び出す事と等価です。
	MsgHsk_SendIdle(pMsgHsk, iMsgType, pMsgData, NULL, NULL);
}
void MsgHsk_SendIdle(ST_MsgHsk* pMsgHsk, int iMsgType/*(-1)以外*/, void* pMsgData/*NULL可*/, void (*idle/*NULL可*/)(void* arg), void* arg) {
//}}2016/09/30追加:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
	//当関数を呼び出したジョブの、ジョブ番号(0～)を取得しておく。
	const int iJob = RRS_i_job(pMsgHsk->pRRS);
#ifdef  DEBUG
	syslog(LOG_DEBUG, "MsgHsk_Send: Job#%d is sending Msg#%d.", iJob, iMsgType);
#endif//DEBUG
	//メッセージタイプとして、(-1)は使用不可です。
	//MsgHsk_Recv()の戻り値として、メッセージが無かった事を示す戻り値(-1)と、区別できなくなるからです。
	if(iMsgType == -1) { DIE(); }
	//送信⇔送信デッドロックならば、エラー停止する。
	if(pMsgHsk->iJobSend != -1) { DIE(); }
	//送信情報を格納する。
	pMsgHsk->iJobSend = iJob;	//送信完了待ち中のジョブ番号(0～)
	pMsgHsk->iMsgType = iMsgType;	//メッセージ番号((-1)以外)
	pMsgHsk->pMsgData = pMsgData;	//メッセージデータポインタ(任意)
	//送信完了を待つ。
//{{2016/09/30変更:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
//	do {
//		RRS_yield(pMsgHsk->pRRS);
////誤	} while(pMsgHsk->iJobSend != -1);	//これは誤り。相手のジョブがMsgHsk_Recv()を呼び出して(pMsgHsk->iJobSend=-1)になった後、相手のジョブがRRS_yield()を呼び出す前にMsgHsk_Send()を呼び出した場合、ここで既に(pMsgHsk->iJobSend=相手のジョブ番号)になっている可能性が有るから。
///*正*/} while(pMsgHsk->iJobSend == iJob);	//こうすれば、(pMsgHsk->iJobSend=-1)になっていても、(pMsgHsk->iJobSend=相手のジョブ番号)になっていても、正しく判定する事が出来る。
//↓2016/09/30変更:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
	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～}ループに変更しました。
		if(idle) { (*idle)(arg); }
	}
//}}2016/09/30変更:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
	//この時点で、MsgHsk_Recv()を呼び出したジョブからの、メッセージデータへのアクセスは終了している。
	//MsgHsk_Send()を呼び出したジョブは、当関数が処理を返したら、メッセージデータを破壊して構わない。
#ifdef  DEBUG
	syslog(LOG_DEBUG, "MsgHsk_Send: Job#%d has sent Msg#%d.", iJob, iMsgType);
#endif//DEBUG
}
/*--------------------------------------------------------------------------*/
//ブロッキングモードで、メッセージを受信する。
//メッセージが無ければ、メッセージが到着するまで待つ。
//当関数が(-1)を返す事は無い。
//(*ppMsgData)を取得した場合、次にRRS_yield()を呼び出した時点で、その内容は相手のジョブによって破棄される可能性が有る事に注意せよ。
int MsgHsk_Recv(ST_MsgHsk* pMsgHsk, void** ppMsgData/*NULL可*/) {
//{{2016/09/30追加:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
	//MsgHsk_Recv()は、アイドリング関数を指定せずにMsgHsk_RecvIdle()を呼び出す事と等価です。
	return MsgHsk_RecvIdle(pMsgHsk, ppMsgData, NULL, NULL);
}
int MsgHsk_RecvIdle(ST_MsgHsk* pMsgHsk, void** ppMsgData/*NULL可*/, void (*idle/*NULL可*/)(void* arg), void* arg) {
//}}2016/09/30追加:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
	//当関数を呼び出したジョブの、ジョブ番号(0～)を取得しておく。
	const int iJob = RRS_i_job(pMsgHsk->pRRS);
	//送信情報が無ければ…
	if(pMsgHsk->iJobSend == -1) {
#ifdef  DEBUG
		syslog(LOG_DEBUG, "MsgHsk_Recv: Job#%d is receiving Msg.", iJob);
#endif//DEBUG
		//受信⇔受信デッドロックならば、エラー停止する。
		if(pMsgHsk->iJobRecv != -1) { DIE(); }
		//受信情報を格納する。
		pMsgHsk->iJobRecv = iJob;
		//送信情報を待つ。
		do {
//{{2016/09/30追加:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
			//アプリケーション定義のアイドリング関数が指定されていたら、アイドリング関数を呼び出す。
			// - アプリケーション定義のアイドリング関数の中では、RRS_yield()を呼び出さないように注意して下さい。(要するに、アイドリング関数としてschedule()を指定してはいけないと言う事です。)	┐
			//   もし、アプリケーション定義のアイドリング関数の中でRRS_yield()が呼び出されると、この下で行うRRS_yield()呼び出しと合わせて、二回RRS_yield()が行われてしまいます。			├【重要】
			//   必ずしも致命的な問題になるとは限らないのですが、このジョブの性能が低下したり、アプリケーションが特定の同期タイミングを想定している場合は致命的な問題になる可能性も有ります。	┘
			// - 既に送信情報が有った場合は、このブロックを通らないので、アイドリング関数を一度も呼び出しません。
			//   そうしないと、MsgHsk_RecvIdle()を呼び出す度に必ずアイドリング関数が一回実行されて、性能が低下してしまう問題が発生するからです。
			if(idle) { (*idle)(arg); }
//}}2016/09/30追加:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
			RRS_yield(pMsgHsk->pRRS);
		} while(pMsgHsk->iJobSend == -1);
		//受信情報をクリアする。
		pMsgHsk->iJobRecv = -1;
	}
#ifdef  DEBUG
	syslog(LOG_DEBUG, "MsgHsk_Recv: Job#%d has received Msg#%d.", iJob, pMsgHsk->iMsgType);
#endif//DEBUG
	//送信情報をクリアする。
	pMsgHsk->iJobSend = -1;
	//メッセージデータポインタを格納する変数が指定されていたら、メッセージデータポインタを格納する。
	// - MsgHsk_Recv()を呼び出したジョブは、次にRRS_yield()を呼び出すまで、メッセージデータの内容に直接アクセス出来ます。
	//   MsgHsk_Recv()を呼び出したジョブが、次にRRS_yield()を呼び出すまで、MsgHsk_Send()を呼び出したジョブの処理が進まない(=MsgHsk_Send()が処理を返さない)ので、メッセージデータの内容は変わらないからです。
	// - MsgHsk_Recv()を呼び出したジョブは、メッセージデータを参照用と見なしても構いませんし、書き込んでも構いません。
	//   MsgHsk_Recv()を呼び出したジョブと、MsgHsk_Send()を呼び出したジョブの、認識が一致していれば、メッセージデータは単なるバッファであり、任意に読み書きする事が出来ます。
	if(ppMsgData) { *ppMsgData = pMsgHsk->pMsgData; }
	//メッセージ番号を返す。
	if(pMsgHsk->iMsgType == -1) { DIE(); }	//MsgHsk_Send()で検査しているので、メッセージ番号として(-1)が格納される事は無いはず。もしここで停止したら当モジュールのバグです。
	return pMsgHsk->iMsgType;
}
/*--------------------------------------------------------------------------*/
//非ブロッキングモードで、メッセージを受信する。
//メッセージが無ければ、メッセージが無かった事を示す戻り値(-1)を返す。
//メッセージが有れば、MsgHsk_Recv()と同じ動作を行う。
int MsgHsk_TryRecv(ST_MsgHsk* pMsgHsk, void** ppMsgData/*NULL可*/) {
	//送信情報が無ければ、メッセージが無かった事を示す戻り値(-1)を返す。
	if(pMsgHsk->iJobSend == -1) { return -1; }
	//ブロッキングモードで、メッセージを受信する。
	//送信情報が有る事を確認済みであるから、ブロッキングされる事は無い。
	return MsgHsk_Recv(pMsgHsk, ppMsgData/*NULL可*/);
}
/*--------------------------------------------------------------------------*/
//ブロッキングモードで、メッセージを受信する。
//受信したメッセージのメッセージタイプが、指定されたメッセージタイプに一致する事を確認する。
//もし違っていたら、当関数内でエラー停止する。
//それ以外は、MsgHsk_Recv()と同じです。
void MsgHsk_Wait(ST_MsgHsk* pMsgHsk, int iMsgType/*(-1)以外*/, void** ppMsgData/*NULL可*/) {
//{{2016/09/30追加:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
	//MsgHsk_Wait()は、アイドリング関数を指定せずにMsgHsk_WaitIdle()を呼び出す事と等価です。
	MsgHsk_WaitIdle(pMsgHsk, iMsgType, ppMsgData, NULL, NULL);
}
void MsgHsk_WaitIdle(ST_MsgHsk* pMsgHsk, int iMsgType/*(-1)以外*/, void** ppMsgData/*NULL可*/, void (*idle/*NULL可*/)(void* arg), void* arg) {
//}}2016/09/30追加:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
#ifdef  DEBUG
	const int iJob = RRS_i_job(pMsgHsk->pRRS);
	syslog(LOG_DEBUG, "MsgHsk_Recv: Job#%d is waiting for Msg#%d.", iJob, iMsgType);
#endif//DEBUG
	//メッセージタイプとして、(-1)は使用不可です。
	//MsgHsk_Recv()の戻り値として、メッセージが無かった事を示す戻り値(-1)と、区別できなくなるからです。
	if(iMsgType == -1) { DIE(); }
	//ブロッキングモードで、メッセージを受信する。
	//受信したメッセージタイプが、指定されたメッセージタイプと異なっていたら、エラー停止する。
//{{2016/09/30変更:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
//	if(MsgHsk_Recv(pMsgHsk, ppMsgData) != iMsgType) { DIE(); }
//↓2016/09/30変更:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
	if(MsgHsk_RecvIdle(pMsgHsk, ppMsgData, idle, arg) != iMsgType) { DIE(); }
//}}2016/09/30変更:MsgHsk_SendIdle(),MsgHsk_RecvIdle(),MsgHsk_WaitIdle()を追加しました。
}
/****************************************************************************
 *	使用例①　片方向通信の例
 ****************************************************************************/
#if 0
ST_MsgHsk stMsgHsk;
void job1_main(ST_RRS* pRRS) {
	int i, iSend = 0x1000;
	char* pData;
	//当関数が呼び出された時点で、まだMsgHsk_Init()は実行されていないので、一回待つ。←←←!!要注意!!
	RRS_yield(pRRS);
	for(;;) {
		//0回以上の任意回数のRRS_yield()を行う。0回でも構わない。
		for(i = rand() % 10; i; i--) { RRS_yield(pRRS); }
		//送信
		pData = strdup_printf("%04X", iSend);	//メッセージデータを作成する。
		MsgHsk_Send(&stMsgHsk, iSend++, pData);	//この関数が処理を返した時点で、相手ジョブのメッセージデータへのアクセスは完了している。
		free(pData);				//メッセージデータを削除する。
	}
}
void job2_main(ST_RRS* pRRS) {
	int i, iRecv;
	char* pData;
	//当関数が呼び出された時点で、まだMsgHsk_Init()は実行されていないので、一回待つ。←←←!!要注意!!
	RRS_yield(pRRS);
	for(;;) {
		//0回以上の任意回数のRRS_yield()を行う。0回でも構わない。
		for(i = rand() % 10; i; i--) { RRS_yield(pRRS); }
		//受信
		iRecv = MsgHsk_Recv(&stMsgHsk, &pData);	//メッセージを受信する。
		printf("%04x: %s\n", iRecv, pData);	//次にRRS_yield()を呼び出すまでの間、メッセージデータへのアクセスが可能である。この例では受信側はメッセージデータを参照用としているが、受信側⇒送信側への受け渡しと見なして書き込んでも構わない。
	}
}
void Test() {
	RRS_new_l(&pRRS, 0/*stacksize*/,
		job1_main,	//ジョブ1
		job2_main,	//ジョブ2
		NULL/*終端*/);
	MsgHsk_Init(&stMsgHsk, pRRS);
	for(;;) { RRS_yield(pRRS); }
}
#endif
/****************************************************************************
 *	使用例②　双方向通信の例
 ****************************************************************************/
#if 0
ST_MsgHsk stMsgHsk;
void job1_main(ST_RRS* pRRS) {
	int i, iRecv, iSend = 0x1000;
	char* pData;
	//当関数が呼び出された時点で、まだMsgHsk_Init()は実行されていないので、一回待つ。←←←!!要注意!!
	RRS_yield(pRRS);
	for(;;) {
		//0回以上の任意回数のRRS_yield()を行う。0回でも構わない。
		for(i = rand() % 10; i; i--) { RRS_yield(pRRS); }
		//送信
		pData = strdup_printf("%04X", iSend);	//メッセージデータを作成する。
		MsgHsk_Send(&stMsgHsk, iSend++, pData);	//この関数が処理を返した時点で、相手ジョブのメッセージデータへのアクセスは完了している。
		free(pData);				//メッセージデータを削除する。
		//0回以上の任意回数のRRS_yield()を行う。0回でも構わない。
		for(i = rand() % 10; i; i--) { RRS_yield(pRRS); }
		//受信
		iRecv = MsgHsk_Recv(&stMsgHsk, &pData);	//メッセージを受信する。
		printf("%04x: %s\n", iRecv, pData);	//次にRRS_yield()を呼び出すまでの間、メッセージデータへのアクセスが可能である。この例では受信側はメッセージデータを参照用としているが、受信側⇒送信側への受け渡しと見なして書き込んでも構わない。
	}
}
void job2_main(ST_RRS* pRRS) {
	int i, iRecv, iSend = 0x2000;
	char* pData;
	//当関数が呼び出された時点で、まだMsgHsk_Init()は実行されていないので、一回待つ。←←←!!要注意!!
	RRS_yield(pRRS);
	for(;;) {
		//0回以上の任意回数のRRS_yield()を行う。0回でも構わない。
		for(i = rand() % 10; i; i--) { RRS_yield(pRRS); }
		//受信
		iRecv = MsgHsk_Recv(&stMsgHsk, &pData);	//メッセージを受信する。
		printf("%04x: %s\n", iRecv, pData);	//次にRRS_yield()を呼び出すまでの間、メッセージデータへのアクセスが可能である。この例では受信側はメッセージデータを参照用としているが、受信側⇒送信側への受け渡しと見なして書き込んでも構わない。
		//0回以上の任意回数のRRS_yield()を行う。0回でも構わない。
		for(i = rand() % 10; i; i--) { RRS_yield(pRRS); }
		//送信
		pData = strdup_printf("%04X", iSend);	//メッセージデータを作成する。
		MsgHsk_Send(&stMsgHsk, iSend++, pData);	//この関数が処理を返した時点で、相手ジョブのメッセージデータへのアクセスは完了している。
		free(pData);				//メッセージデータを削除する。
	}
}
void Test() {
	RRS_new_l(&pRRS, 0/*stacksize*/,
		job1_main,	//ジョブ1
		job2_main,	//ジョブ2
		NULL/*終端*/);
	MsgHsk_Init(&stMsgHsk, pRRS);
	for(;;) { RRS_yield(pRRS); }
}
#endif
