module y4d_thread.microthread;

///	マイクロスレッド。(Win32のfiber的なもの)
/**
	このクラスを通じて呼び出した関数は、関数の途中でresumeし、
	次に呼び出されたときに、以前の状態を再現することが出来る

	プログラム例
<PRE>
	class Character {
		void	onMove(){ microthread_.onMove(); }
		void	microThreadMain(){
			int i,x=100,y=200;
			for(i=0;i<10;++i) { x+=10; print(x,y); suspend(); }
			for(i=0;i<10;++i) { y+=10; print(x,y); suspend(); }
			printf("%d stack\n",microthread_.getUseStackSize());
		}
		void	print(int x,int y) { printf("(%d,%d)\n",x,y); }

		this() { microthread_ = new MicroThread(&microThreadMain); }
	protected:
		void suspend() { microthread_.suspend(); }
	private:
		MicroThread microthread_;
	};

	int main(){
		Character c = new Character;
		while(1) {
			c.onMove();
			printf("move--\n");
		}
		return 0;
	}
</PRE>

	このようにすれば、onMoveを呼び出すごとに、
	x+=10を10回onMoveを呼び出す間、繰り返し
	11回目のonMoveの呼び出しからは、y+=10を実行する。
	それを10回繰り返してマイクロスレッドは終了します。
	(以降、onMoveを呼び出しても何も起こりません)

	注意事項：
		マイクロスレッド内で例外は扱えません。

	@todo :
		マイクロスレッド実行中にGCが動くとまずい。
		GCをdisableにする必要があるのだけど、DMDコンパイラではまだサポート
		されていない。

*/
class MicroThread {
public:

	/**
		マイクロスレッドの実行を開始する。
		スタックサイズはディフォルトで0x1000バイト。(しか確保していない)
		これをしていない状態でsuspendとかresumeとか呼び出してはいけない。
		（debugビルドとreleaseビルドでサイズを切り替えても良い）
		Dコンパイラの場合、小さな関数ならば100バイトほどしか食わないようだ。
	*/
	void	start(void delegate() start_func,int nStackSize/*=0x1000*/)
	{
		//	指定があれば、それで上書き
		if (nStackSize) nStackSize_ = nStackSize;
		if (!nStackSize_) nStackSize_ = 0x1000;

		//	本当はスタック上に確保しないといけないのだが
		
		//	すでに確保済みならば、再度確保する必要はない。
		if (abyStack_.length < nStackSize_) 
		{
			abyStack_ = new byte[nStackSize_];
		}
		register_esp_ = &abyStack_[0] + nStackSize_;

		bEnd_ = false; bSuspended_ = false;

		register_esp_start_ = register_esp_;
		start_func_ = start_func;
		/*
			xchg	ESP,[frame];
			pop		EDI;
			pop		ESI;
			pop		EBP;
			pop		EBX;
			ret;			//	callしたいアドレス
		*/
		//	↑の逆順で仮想スタック上にpushしておけばいいのだ

		ms_push(cast(uint)(&start_));
		ms_push(0); // ebx
		ms_push(0); // ebp
		ms_push(0); // esi
		ms_push(0); // edi

		switchThread();
	}

	void	start(void delegate () start_func)
	{ start(start_func,0); }

	///	実行中の処理を中断する
	void	suspend()
	{
		bSuspended_ = true;
		switchThread();
	}

	/// 実行中の処理をn回中断する。suspendをn回呼び出すと考えれば良い
	void	sleep(int n)
	{	while(--n >= 0) suspend(); }

	///	中断した処理を再開する
	void	resume()
	{
		if (!isSuspended()) return ;	//	中断（･Ａ･）ｼﾃﾅｲ!!
		bSuspended_ = false;
		switchThread();
	}

	///	サスペンド中か？
	bool	isSuspended() { return bSuspended_;}

	///	マイクロスレッドの実行は終了したか？
	bool	isEnd() { return bEnd_; }

	/**
		現在マイクロスレッドが使用しているスタックサイズを返す
		(マイクロスレッド内からは呼び出しても無意味)
		マイクロスレッド内から呼び出して、どれくらいのメモリを食っているのか
		チェックしておくことをお勧めします。(Debugビルド時とReleaseビルド時とで
		えらく違うので注意が必要)
	*/
	///	使用しているスタックサイズのチェック
	uint		getUseStackSize()
	{
		int	x;	//	この変数までのサイズを検出
		return cast(uint)register_esp_start_ - cast(uint)(&x);
	}

	///	マイクロスレッドを一回分動かす
	/**
		この関数は、以下のものと等価。
	<PRE>
	void	onMove() {
		if (!isEnd()){
			if (!isSuspended()) {
				start(start_func_);
			} else {
				resume();
			}
		}
	}
	</PRE>
	要するに、１回目ならば、コンストラクタで指定された関数を呼び出し、
	suspend中ならば、resumeし、関数の実行が終了していれば何もせずに戻ります。
	*/
	void	onMove() {
		if (!isEnd()){
			if (!isSuspended()) {
				start(start_func_);
			} else {
				resume();
			}
		}
	}

	///	start関数を自前で呼び出すときの何も初期化しないコンストラクタ
	this() {}

	///	onMoveを呼び出されたときに実行する関数を指定しておく
	/**
		StackSizeはディフォルトの0x1000。
	*/
	this(void delegate () start_func)
	{ start_func_ = start_func; }

	///	onMoveを呼び出されたときに実行する関数とMicroThreadのスタックサイズを指定しておく
	this(void delegate () start_func,int nStackSize)
	{ start_func_ = start_func; nStackSize_ = nStackSize; }

private:
	byte*			register_esp_;		//	記録しているESP
	byte*			register_esp_start_;//	開始時のESP
	bool			bSuspended_;		//	マイクロスレッドのサスペンド
	bool			bEnd_;				//	マイクロスレッドの終了フラグ
	byte[]			abyStack_;			//	スタック
	int				nStackSize_;		//	スタックサイズの指定

	void	delegate() start_func_;		//	最初に呼び出すfunction

	//	delegateへのjump台
	static void start_(MicroThread mt) {
		mt.start_func_();
		mt.bEnd_ = true; // MicroThreadの実行が終了した
		mt.switchThread();
		//	もう↓が実行されることは無い
	}

	//	マイクロスレッド用のスタックにpushしていく
	void	ms_push(uint u)
	{
		register_esp_ -= 4;
		*cast(uint*)(register_esp_) = u;
	}

	void	switchThread(){
		//	naked functionのなかでクラスメンバへの変数を代入には
		//	失敗するらしい>DMD (bug?)

		asm {
			naked;					//	このnakedは有効らしい??
//			mov		EAX,pThis;

	//	naked functionならばEAXにはthisが入っていると仮定して良いらしい
	//	それなら、[EAX+8]がこのクラスで宣言されているの一つ目の変数
	//	(register_esp_)だと仮定していい

			push	EBX;
			push	EBP;
			push	ESI;
			push	EDI;

//			push	dword ptr FS:[0];	//	例外処理のために必要

			xchg	ESP,[EAX+8];

//			pop		dword ptr FS:[0];	//	例外処理のために必要

			pop		EDI;
			pop		ESI;
			pop		EBP;
			pop		EBX;
			ret;
		}
	}

/+
	static void	quitFunction(){
	//	アセンブラから呼び出す例外を発生する関数
			throw new MicroThreadQuitException();
	}

	//	マイクロスレッド内部で用いる例外クラス
	class MicroThreadQuitException {};

	void	releaseStack();
+/
};

