非ぃ同期処理があったなんて!!

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

非ぃ同期処理があったなんて!!

投稿記事 by せんちゃ » 12年前

DXライブラリに非同期でロードする機能が追加されていてびっくり。
というわけでさっそく実装テストしてみる。

ついでに状態管理の方法もいっしょにまとめる。
質問掲示板はあまり見ないのだけど最近見ていたら、
状態の切り替えに四苦八苦している人が結構いらっしゃるので自分なりの方法を一応書いてみます。


まずファイル読み込み、
ちょっと重いファイルをたくさんロードしてみます

CODE:

//	画像リソース管理
struct GraphicResource {
private :
	int m_handle;
	int m_initialized;
	const char* m_filepath;
public :
	GraphicResource(){
		m_handle = 0;
		m_initialized = false;
		m_filepath = NULL;
	}
	~GraphicResource(){
		APP_ASSERT( !m_initialized );
	}

	// ロード処理
	void load( const char* filepath ){
		if( !filepath ) return;
		SetUseASyncLoadFlag( TRUE )    // 非同期読み込みON
		m_filepath = filepath;                // ファイルパスを指定して
		m_handle   = LoadGraph( m_filepath ); // ロード開始
	}

	// ロードの同期をとる
	bool loadSync(){
		int result = CheckHandleASyncLoad( m_handle );
		if( result == 1 ) return false;  // 1は読み込み中らしい
		if( result == 0 ){               // 0が返ってきたら正常終了
			m_initialized = true;
			return true;   
		}
		//	それ以外の値が返ってきたら異常である
		APP_PRINT( "LoadGraph Failed [m_handle %d][m_filepath %s] \n" , m_handle , m_filepath );
		return false;
	}

	// 終了処理
	void finalize(){
		if( m_initialized ){
			m_initialized = false;
			DeleteGraph( m_handle );
		}
	}

	// 画像ハンドル取得
	const int& getHandle(){
		return m_handle;
	}
};
テスト用に書いたリソースクラス。


CODE:

SetUseASyncLoadFlag

CODE:

CheckHandleASyncLoad
という関数で非同期フラグを立てたり、
読み込み完了通知を知ることができるようです

すごい!
loadSync()はロードが完了するまでfalseを投げる関数。
完了したらtrueを投げます。



そして次にこれを振り回すシーンを作ります。
私がよく書くシーンのライフサイクルはこんな感じです。

CODE:

//	シーン管理
class Scene {
private :
	unsigned int m_frametime;
	int m_state;
	int m_statenext;
	bool m_isfirst;
public :
	enum {
		STATE_NONE = 0 ,
		STATE_INITIALIZE , 
		STATE_LOAD , 
		STATE_SYNC , 
		STATE_SETUP , 
		STATE_PROCESS ,
		STATE_NUM
	};

	Scene(){
		m_frametime = 0;
		m_state     = STATE_NONE;
		m_statenext = STATE_INITIALIZE;
		m_isfirst   = false;
	}
	virtual ~Scene(){
	}

	void changeState( int statenext ){
		m_statenext = statenext;
	}

	virtual void update(){
		if( m_statenext != m_state ){
			m_state     = m_statenext;
			m_isfirst   = true;
			m_frametime = 0;
		}

		switch ( m_state ){
		case STATE_INITIALIZE : 
			initialize(); 
			changeState( STATE_LOAD );
			break;
		case STATE_LOAD :
			load();
			changeState( STATE_SYNC );
			break;
		case STATE_SYNC :
			if( !sync() ) break;
			changeState( STATE_SETUP );
			break;
		case STATE_SETUP :
			setup();
			changeState( STATE_PROCESS );
			break;
		case STATE_PROCESS :
			process();
			break;
		}
                m_isfirst = false;
		m_frametime++;
	}
	virtual void initialize() {}
	virtual void load(){}
	virtual bool sync(){}
	virtual void setup(){}
	virtual void process(){}
};

virtual void initialize() {}
virtual void load(){}
virtual bool sync(){}
virtual void setup(){}
virtual void process(){}

は継承先での実装となる。
状態の切り替えはm_stateとm_statenextからコントロールする。
m_statenextに新しい状態がやってきた場合、変化タイミングとみなして初回フラグを立てる。
各ステートは切り替わり時の初回タイミングを手に入れることができます。
いわゆるトリガーですが、これを手に入れるタイミングってなかなかわかりづらいですよね。


initialize,load,setupは一回しか呼ばれない。それぞれのタイミングで必要な初期設定を行う
syncは読み込み同期が完了したと認識するまで動かない
processは更新処理、ゲームループです。
これらの処理は継承先で実装、基底のことは継承先は知らなくてもいいようにします。



CODE:

// 画像読み込み処理サンプル
class MainScene : public Scene {
private :
	GraphicResource m_gRes_1;
	GraphicResource m_gRes_2;
	GraphicResource m_gRes_3;
	GraphicResource m_gRes_4;
public :
	~MainScene(){
		m_gRes_1.finalize();
		m_gRes_2.finalize();
		m_gRes_3.finalize();
		m_gRes_4.finalize();
	}

	virtual void initialize(){
	}

	virtual void load(){
		m_gRes_1.load( "img/01.png" );
		m_gRes_2.load( "img/02.png" );
		m_gRes_3.load( "img/03.png" );
		m_gRes_4.load( "img/04.png" );
	}

	virtual bool sync(){
		if( !m_gRes_1.loadSync() ) return false;
		if( !m_gRes_2.loadSync() ) return false;
		if( !m_gRes_3.loadSync() ) return false;
		if( !m_gRes_4.loadSync() ) return false;
		return true;
	}

	virtual void setup(){
	}

	virtual void process(){
		DrawGraph( 0 , 0 , m_gRes_1.getHandle() , TRUE );
	}
};

画像インスタンスを適当に作ってロードして描画するだけという簡単なシーン
挙動を確認しています。
メインには大したことは書いていないので割愛。



とりあえず状態管理と非同期処理と同期をとる方法を書いてみる。

アバター
へにっくす
記事: 634
登録日時: 13年前

Re: 非ぃ同期処理があったなんて!!

投稿記事 by へにっくす » 12年前

loadSyncメソッドの最初にm_handleが0かどうかの判定を入れた方がよいと思います。
loadする前にloadSync呼ばれる可能性がないなら別にいいんですけど、私なら使用する変数が意図されている状態かどうか、必ずチェックしますよ。
最後に編集したユーザー へにっくす on 2013年4月20日(土) 10:35 [ 編集 1 回目 ]

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

Re: 非ぃ同期処理があったなんて!!

投稿記事 by せんちゃ » 12年前

へにっくすさん
ハンドルのバリデーションはCheckHandleASyncLoadで行われてて、
有効じゃないハンドル番号だと-1が返るので必要ないかなと思いました。
loadSyncが呼ばれるタイミングが正しくない、ファイルパスが間違っている、などの場合はそれ以上処理は進みません

ISLe
記事: 2650
登録日時: 15年前

Re: 非ぃ同期処理があったなんて!!

投稿記事 by ISLe » 12年前

シーンごとにステートを持つんですね。
わたしはステートごとにもタスクを分けてしまいますけど。

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

Re: 非ぃ同期処理があったなんて!!

投稿記事 by せんちゃ » 12年前

ISLeさん
あっちこっちのシーンで使いまわす系のモノになると最終的にタスクにしてしまう場合も多いのですが、
シーン内でステートを切ることが多いですね。
仕事でもそう書いているのですが、この方法だと後々仕様変更によってひとつのシーンで遷移しなければいけない場所が
どっさり来てクラスが太ってしまうことがあるので、そういったときの対処方法を最近考えています。

ISLe
記事: 2650
登録日時: 15年前

Re: 非ぃ同期処理があったなんて!!

投稿記事 by ISLe » 12年前

せんちゃ さんが書きました:仕事でもそう書いているのですが、この方法だと後々仕様変更によってひとつのシーンで遷移しなければいけない場所が
どっさり来てクラスが太ってしまうことがあるので、そういったときの対処方法を最近考えています。
わたしだとシーンを管理するタスクと管理されるタスクで分けますね。

せんちゃさんのおっしゃるシーンは管理するタスクに該当します。
管理されるタスクに対するイベント処理を請け負わせます。

管理されるタスクはシーンとは別のライフサイクルになります。
スマートポインタを使って参照が無くなったら消滅するだけですが。

初期化から後始末まで同様に、必要に応じて管理するタスクを作成しイベントが終われば管理するタスクは消滅します。
遷移対象は最初から分離してあって、増えても機能別に分割してリストに登録してまとめて管理します。
自作のフレームワークではタスク同士が直接双方向リストで繋がるので、どこに繋ぐかだけで簡単です。

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

Re: 非ぃ同期処理があったなんて!!

投稿記事 by せんちゃ » 12年前

Stateパターンのようなものをシーンの中に入れているような感じでしょうか?
それぞれの管理をタスクに分けて必要に応じて走らせそれぞれのタスクが必要な処理を終えたら次にどこへいくのかを決める、というような

その場合、一番上のタスクを下のタスクは参照で持っている感じになるんですかね?
初期化するときや、上のタスクを操作するときの方法がちょっと浮かばないです。。。

ISLe
記事: 2650
登録日時: 15年前

Re: 非ぃ同期処理があったなんて!!

投稿記事 by ISLe » 12年前

遅延ロードの場合、前のシーンの解放処理と進捗を同期しないといけなかったりすることも多いですし、その部分をタスクにするという感じです。
遅延がなければメソッドの呼び出し一回で済んでしまうわけですし。

例えば初期化関数が独立してコルーチンになってる感覚と言ったら余計分かりにくいですかね。

もちろん参照を持って参照先の処理を進めることになります。

Stateパターンはオブジェクトが状態を保持するものですが、こちらはひとつの状態をひとつのタスクで表現します。

タスクに分けるとタスクが存在するかどうかで振り分けられるので制御構造もシンプルになります。

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

Re: 非ぃ同期処理があったなんて!!

投稿記事 by せんちゃ » 12年前

ISLeさん
>例えば初期化関数が独立してコルーチンになってる感覚と言ったら余計分かりにくいですかね。

なんとなく言わんとしていることがわかったような気がします。
各状態そのものを独立したタスクとしてメイン処理(管理タスク)とは別に行うということですかね。

ISLe
記事: 2650
登録日時: 15年前

Re: 非ぃ同期処理があったなんて!!

投稿記事 by ISLe » 12年前

せんちゃ さんが書きました:各状態そのものを独立したタスクとしてメイン処理(管理タスク)とは別に行うということですかね。
そういうことですね。

用語の使い方の違いですかね。
わたしは状態遷移を行う専門のタスクを管理するタスクと呼んでいます。
管理されるタスクがメイン処理の一部で、管理タスクがない状態(準備完了状態)で機能します。
どの辺がいつ機能するとかしないとか細かいことはタスク内部やタスク間での調整次第ですが。

ゲームのメイン処理は既に準備されている(そこに固定されている)ものとして考えないとややこしくないですか?
遅延ロードとかって一時停止と同じ考え方ができるので、その時間は無いものとしてメイン処理を書いたほうがスッキリすると思いますけど。

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

Re: 非ぃ同期処理があったなんて!!

投稿記事 by せんちゃ » 12年前

以前に会社の先輩と「どうぶつの森規模のゲームだとリソースは動的読み込みじゃないとまず無理だよね」
といった話題になったことがありますが。。。
「状態をタスクにする」というのは動的読み込みのヒントになりそうな気がしています。

メイン処理を走らせる一方でバックで読み込みタスクも走らせて
読み込みが終わったものからどんどんと描画していくみたいな。

アプリのメイン処理とリソースの読み込み同期はできる限り意識しないでもいいように作れるのが
理想的であるというのは非常に納得です。
最後に編集したユーザー せんちゃ on 2013年4月21日(日) 01:50 [ 編集 1 回目 ]