BREW で MicroThread(2)
MicroThread は、スタック切り替えで関数を実行途中で停止出来るようにすることによって実現出来ます。
id:yaneurao さんのやね本2にこの実装が詳しく書かれています。
Windowsプロフェッショナルゲームプログラミング2【CD-ROM付】 (Game developer books)
- 作者: やねうらお
- 出版社/メーカー: 秀和システム
- 発売日: 2003/09/01
- メディア: 単行本
- 購入: 1人 クリック: 35回
- この商品を含むブログ (35件) を見る
自分もほとんど同じ実装だったり……。
当然スタック切り替えなんて C++ では出来ないので、ARM アセンブラで書くことになります。
AREA ||i.SwitchThread||, CODE, READONLY EXPORT SwitchThread SwitchThread ; r0 には MicroThread へのポインタが入っている STMFD sp!,{r0-r12,lr} ; sp と pc 以外のレジスタをバックアップ ADD r4,r0,#4 ; r0 は仮想テーブルの位置なので、 ; +4 してスタックへのポインタを取得する SWP sp,sp,[r4] ; スタックポインタを交換(`・ω・´) LDMFD sp!,{r0-r12,lr} ; sp と pc 以外のレジスタをリストア MOV pc,lr ; (・∀・)カエレ!!
確かバックアップを取る必要のないレジスタもあったと思うんですが、どうせ1命令なので(スタックポインタとプログラムカウンタ以外)全部保存してしまってます。
で、SWP でスタックポインタを交換して、バックアップしておいたレジスタを復元するだけです。
あと、最初は組み込みアセンブラを使おうと思っていたんですが、どうやら RVCT for BREW v1.2 だと対応してないっぽいです。
なので結局普通の(?)アセンブラです(;´Д`)
以下が現在の実装。
class Runnable{ public: virtual ~Runnable(){} virtual void run() = 0; }; class MicroThread : public Runnable{ public: MicroThread(); virtual ~MicroThread(); virtual void start(); virtual void suspend(); virtual void resume(); virtual bool isExecute(); virtual bool isSuspend(); private: byte* _pStack; // スタックへのポインタ int _bExecute; // ARM asm で書きやすいので int 型 byte _stack[ 1024 * 32 ]; bool _bSuspend; static void StartCallback( MicroThread* pThread ); };
extern "C" void StartThread( MicroThread* , void (*callback)( MicroThread* ) ); extern "C" void SwitchThread( MicroThread* ); MicroThread::MicroThread() : _pStack( null ) , _bSuspend( false ) , _bExecute( 0 ){ } MicroThread::~MicroThread(){ } void MicroThread::start(){ if( isExecute() ) return; _pStack = &_stack[ 1024 * 32 ]; StartThread( this , &StartCallback ); } void MicroThread::suspend(){ if( isSuspend() || !isExecute() ) return; _bSuspend = true; SwitchThread( this ); } void MicroThread::resume(){ if( !isSuspend() ) return; _bSuspend = false; SwitchThread( this ); } bool MicroThread::isExecute(){ return (_bExecute != 0); } bool MicroThread::isSuspend(){ return _bSuspend; } void MicroThread::StartCallback( MicroThread* pThread ){ pThread->run(); }
AREA ||i.StartThread||, CODE, READONLY EXPORT StartThread StartThread ; r0 には MicroThread へのポインタが入っている ; r1 にはコールバック関数へのポインタが入っている STMFD sp!,{r0-r12,lr} ; sp と pc 以外のレジスタをバックアップ ADD r4,r0,#4 ; r0 は仮想テーブルの位置なので、 ; +4 してスタックへのポインタを取得する SWP sp,sp,[r4] ; スタックポインタを交換(`・ω・´) MOV r5,#1 ; 実行状態を実行中に変更(_bExecute = 1) STR r5,[r0,#8] ; r0 + 8 の場所に _bExecute(b を付けてるけどint型) がある STR r0,[sp,#-4]! ; スタックに r0 をプッシュ MOV lr,pc ; BLX が未サポートらしいので、 BX r1 ; lr を保存して BX でジャンプ ; 引数は r0 ; ここから下は正常に run() を抜けた場合しか通らない ; なぜなら、run() を実行して1回目の suspend() で SwitchThread を実行して、 ; 最後の MOV で StartThread の呼び出し元へジャンプするから。 LDR r0,[sp],#4 ; スタックから r0 をポップ MOV r5,#0 ; 実行状態を非実行中に変更(_bExecute = 0) STR r5,[r0,#8] ; r0 + 8 の場所に _bExecute(b を付けてるけどint型) がある ADD r4,r0,#4 ; r0 は仮想テーブルの位置なので、 ; +4 してスタックへのポインタを取得する SWP sp,sp,[r4] ; スタックポインタを交換(`・ω・´) LDMFD sp!,{r0-r12,lr} ; sp と pc 以外のレジスタをリストア MOV pc,lr ; (・∀・)カエレ!! AREA ||i.SwitchThread||, CODE, READONLY EXPORT SwitchThread SwitchThread ; r0 には MicroThread へのポインタが入っている STMFD sp!,{r0-r12,lr} ; sp と pc 以外のレジスタをバックアップ ADD r4,r0,#4 ; r0 は仮想テーブルの位置なので、 ; +4 してスタックへのポインタを取得する SWP sp,sp,[r4] ; スタックポインタを交換(`・ω・´) LDMFD sp!,{r0-r12,lr} ; sp と pc 以外のレジスタをリストア MOV pc,lr ; (・∀・)カエレ!! END
MALLOC() してやるのが面倒なので(ぇ、メンバに MicroThread 用のスタックを持っています。
で、使ってない方のスタックポインタを _pStack に保存しておいて、suspend() や resume() が呼び出されるたびに _pStack とスタックポインタを交換してやってます。
短いソースなので実装はそんなに難しくないんですが、どっちかっていうとアセンブラの書き方がわからなくて苦戦したという罠(;´Д`)
あとは昨日のデストラクタが呼ばれない問題か……ARM は例外が使えないし、どうしよう(;´Д`)