Boostのビルドオプション
毎度アレなので、健忘録的メモ
b2.exe install toolset=msvc-10.0 link=static runtime-link=static variant=debug,release threading=multi
Boost.Coroutine
Boost.CoroutineがBoost 1.55で新しいインターフェイスを搭載したとの事なので、何をどう書けばどう動くかを書いてみます。
pull_type/push_type?
今までのBoost.Coroutineは何か良く分からないシグネチャを持ったcoroutine関連らしきクラスを引数として、それを双方向にぐにゃぐにゃやる必要があって非常に面倒でした。
しかし流石に、作者の人も使いにくいのが分かっていたらしく、今回大幅に整理してくれたようです。
まず、双方向なアクセスが出来たクラス(caller_type)を廃止し、値を外に返す側(push_type)と値を取ってくる側(pull_type)の片方向x2に変更されました。
基本的には、
// 値を渡す側 boost::coroutines::coroutine<渡したい値の型>::push_type // 値を受け取る側 boost::coroutines::coroutine<受け取りたい値の型>::pull_type // ※当然ながら、「渡したい値の型==受け取りたい値の型」であること。
の2つを対にして使う、と覚えておけばいいと思います。
それでは、値を受け取る方向のサンプルコードと、値を渡す方向のサンプルコードを書いてみます。
ジェネレータ
まず、Coroutineの一般的な用途でよく使われそうな【値を1個ずつ生成する関数】(値を受け取る方向)を書く方法から。
いわゆる「ジェネレータ」という奴で、必要になる度に1個ずつ値を取ってきたい時に使う奴ですね。
#include <iostream> #include <boost/coroutine/all.hpp> using namespace std; // 実行する関数 void Action(boost::coroutines::coroutine<int>::push_type& yield) { // 1回呼ばれる毎に0,1,2,3,4 と値を1つずつ返す for (int i = 0; i < 5; i++) { yield(i); } } // range-based for void Sample1() { boost::coroutines::coroutine<int>::pull_type co(Action); cout << "start loop." << endl; for (auto& item : co) { cout << item << endl; } } // イテレータアクセス void Sample2() { boost::coroutines::coroutine<int>::pull_type co(Action); cout << "start loop." << endl; for (auto it = boost::begin(co); it != boost::end(co); ++it) { cout << (*it) << endl; } } // 値取れるかチェック->get->次のyield void Sample3() { boost::coroutines::coroutine<int>::pull_type co(Action); cout << "start loop." << endl; while (co.has_result()) { cout << co.get() << endl; co(); } } int main() { cout << "- - - - - - - " << endl; Sample1(); cout << "- - - - - - - " << endl; Sample2(); cout << "- - - - - - - " << endl; Sample3(); cout << "- - - - - - - " << endl; return 0; }
出力結果:
- - - - - - - start loop. 0 1 2 3 4 - - - - - - - start loop. 0 1 2 3 4 - - - - - - - start loop. 0 1 2 3 4 - - - - - - -
数値を渡していくやつ
次は、(こういうアレの名前知らないんですが…)値を渡していく奴を書いてみます。
処理としては、値を1つずつ関数に渡していって、関数は受け取った値と、今までの合計を標準出力に吐き出し続ける感じ(値を渡す方向)です。
先ほどのコードとは、pull_typeとpush_typeの位置が逆転してる事が分かります。
#include <iostream> #include <boost/coroutine/all.hpp> using namespace std; // 実行する関数1 void Sample1(boost::coroutines::coroutine<int>::pull_type& co) { int total = 0; for(auto& item : co) { cout << item << endl; total += item; cout << "total:" << total << endl; } } // 実行する関数2 void Sample2(boost::coroutines::coroutine<int>::pull_type& co) { int total = 0; for (auto it = boost::begin(co); it != boost::end(co); ++it) { cout << (*it) << endl; total += (*it); cout << "total:" << total << endl; } } // 実行する関数3 void Sample3(boost::coroutines::coroutine<int>::pull_type& co) { int total = 0; while (co.has_result()){ cout << co.get() << endl; total += co.get(); cout << "total:" << total << endl; co(); } } int main() { cout << "- - - - - - - " << endl; boost::coroutines::coroutine<int>::push_type yield1(Sample1); cout << "start loop." << endl; for (int i = 1; i <= 5; i++) { yield1(i); } cout << "- - - - - - - " << endl; boost::coroutines::coroutine<int>::push_type yield2(Sample2); cout << "start loop." << endl; for (int i = 1; i <= 5; i++) { yield2(i); } cout << "- - - - - - - " << endl; boost::coroutines::coroutine<int>::push_type yield3(Sample3); cout << "start loop." << endl; for (int i = 1; i <= 5; i++) { yield3(i); } return 0; }
出力結果:
- - - - - - - start loop. 1 total:1 2 total:3 3 total:6 4 total:10 5 total:15 - - - - - - - start loop. 1 total:1 2 total:3 3 total:6 4 total:10 5 total:15 - - - - - - - start loop. 1 total:1 2 total:3 3 total:6 4 total:10 5 total:15
実に対照的なので分かりやすい。
動的ゲームでよく使いたい奴
そして(個人的には)本題の、動的ゲームでよく使うような使い方。
DXライブラリ導入してコピペで動くヤツが以下。
#include "DxLib.h" #include <boost/coroutine/all.hpp> class Actor{ public: Actor() : co(Action) {} void Update() { // カウントが0以下の時、Actionを実行 if (--count <= 0) { co(this); } } void Draw() { DrawCircle(x, y, 30, GetColor(0, 0, 255), TRUE); } private: static void Action(boost::coroutines::coroutine<Actor*>::pull_type& yield) { while (true) { // x方向に5秒使って100px進む for (size_t i = 0; i < 5 * 60; i++) { yield.get()->x++; yield(); } // 1秒止まる yield.get()->count = 60; yield(); // y方向に5秒使って100px進む for (size_t i = 0; i < 5 * 60; i++) { yield.get()->y++; yield(); } // 1秒止まる yield.get()->count = 60; yield(); // x方向に5秒使って100px戻る for (size_t i = 0; i < 5 * 60; i++) { yield.get()->x--; yield(); } // 1秒止まる yield.get()->count = 60; yield(); // y方向に5秒使って100px戻る for (size_t i = 0; i < 5 * 60; i++) { yield.get()->y--; yield(); } // 1秒止まる yield.get()->count = 60; yield(); } } private: int count = 0; int x = 100; int y = 100; boost::coroutines::coroutine<Actor*>::push_type co; }; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // ウインドウモードに変更 ChangeWindowMode(TRUE); if (DxLib_Init() == -1) // DXライブラリ初期化処理 { return -1; // エラーが起きたら直ちに終了 } SetDrawScreen(DX_SCREEN_BACK); // Actor実体化 Actor actor; // Zキーの入力待ち while (CheckHitKey(KEY_INPUT_Z) == 0 && ProcessMessage() != -1) { ClearDrawScreen(); // □な感じに移動し続ける actor.Update(); actor.Draw(); ScreenFlip(); } DxLib_End(); // DXライブラリ使用の終了処理 return 0; // ソフトの終了 }
"yield.get()->"が凄く汚くて嫌なので、たぶん実際にゲーム用のコード書くときはもっとマシな方法模索すると思いますが…。
まあ、そんな感じで一応動くんじゃないでしょうか!
あまりにも更新していない
このままだとブログに埃が積もってしまうので、C++に限らず月一くらいで何か書いていきたい(戒め)
Boost.Coroutineの速度
前略。
http://d.hatena.ne.jp/joynote/20121207/1354863235
この記事のコードを改造して、生でカウンタとif文で動きを制御するコードを追加。
コルーチンを使った場合と、煩雑になるが生で制御した場合の速度差を実測した。
検証コードが厳密じゃない(タイマーの実行コスト等の考慮足りない)のでコードは割愛するが、
(あとでちゃんとした検証と共に載せたい)
同じ動作を完了するのに、
・1.41倍程度コルーチンの方が時間がかかる
という結果が出た。
1.41倍なら流石にLuaJITでもXtalでも完全に追いつけない速度なので、スクリプト言語で動作を記述して、遅いようならC++生で書くという作戦が上手くいきそうな手応えを感じた。
というメモ。
Boost.Poolを試してみた
ゲームで使いたいBoost.Pool…だけど聞こえてくる噂はどうにも微妙…?
全ゲ連の講演とかでも時折聞いたりするものの、イマイチ「いいよ!! マジおすすめ!」っていう程のトーンで話を聞かない。
そんな中、ゲームプログラミングにおけるC++の都市伝説がTwitterで流れてきたので読んでいたら、なんとBoost.Poolの話があった!
それで、いろいろ納得できたり、もうちょっと挙動知りたかったりだったので、自分で試してみた。
Boost.Poolは内部で確保したメモリ領域を小分けにして使うことでnewが早く………
とか、よく聞くのだけれど、つまりは
「事前に用意してあるメモリ領域に」
「変なコトにならないように自動で」
「placement newする」
みたいなものっぽい。
で、パフォーマンス的にどんなもんじゃろ? という事で実測してみた。
10万回newして、毎回どのくらい時間かかっているかを出力。
コードがアレだけど見逃して…
#include <iostream> #include <memory> #include <list> #include <boost/lexical_cast.hpp> #include <boost/pool/object_pool.hpp> #include <boost/chrono/system_clocks.hpp> typedef boost::chrono::high_resolution_clock clock_type; class Hoge{ public: Hoge(std::string name) { static int i=0; i++; name_ = name + boost::lexical_cast<std::string>(i); } void print() { std::cout << name_ << std::endl; } ~Hoge() { // std::cout << name_ << " deleted" << std::endl; } private: std::string name_; char tmp[1024]; }; #include <fstream> int main() { std::ofstream ofs("test.csv", std::ios::app | std::ios::ate); for(int s=0; s < 1; ++s){ boost::object_pool<Hoge> mem_pool(100000); { // Hogeを格納するlist std::list<std::shared_ptr<Hoge>> hoge_list; for(int i=0; i < 100000; ++i){ clock_type::time_point start = clock_type::now(); #ifndef _ // new (pool) Hoge* tmp = new Hoge("taro"); // shared_ptrにくるむ auto object = std::shared_ptr<Hoge>( tmp); #else // new (pool) Hoge* tmp = mem_pool.construct("taro"); // shared_ptrにくるむ auto object = std::shared_ptr<Hoge>( tmp, // 通常のdeleteはできないので、デリータで解放するようにしておく // ラムダ式にはmem_poolの参照をバインドしておく [&mem_pool](Hoge* hoge) { mem_pool.destroy(hoge); } ); #endif // push_back hoge_list.push_back( object ); boost::chrono::nanoseconds elapsed = clock_type::now() - start; ofs << i << "," << elapsed.count() << "," << std::endl; } }// hoge_list内のオブジェクト全てのデストラクタが走る } std::cout << "END" << std::endl; return 0; }
結果は一番下に載せておく。
結果から見ると、メモリ領域の確保はstd::vectorと同じく「足りなくなったら既存領域のn倍を再確保」しているっぽい。MSVCでやったので、nは1.5。gccなら2。
ナマでnewするのに比べて、newにかかる時間の振れ幅はかなり抑えられているようだ。
最初の確保数を「1」とかの小さな値にすると、再確保が起きる度にガクッと処理が遅くなるので、これが所謂「時折ガクッとなるからゲームには使えない」という噂のモトっぽい。
結論としては、
「newするたびにカクッとなられるのは困るんだけど、自分でアロケータ書くの嫌だああああ! という時には使えるんじゃないですかね?」
という感じでした。
平均して凄い時間かかってるように見えるが、横は10万要素あるので「n個に1個の特別時間かかったの」が見えているだけ。逆に言えば、見えてない下のは「n個に1個」の頻度が少ないことがわかる。
最初に全部領域を確保しているせいか、最初だけアホみたいに重い。
やってはいけない典型例。無駄な再確保が行われている。あと1個要素を追加すれば、再確保が起きて一個上の完全劣化。
Boost.Coroutineが正式に入ったので
Boost.Coroutineが正式にBoost入りしそうなので、Boost Vaultから試作品のBoost.Coroutineを持ってきて試してみた(※正式に入る版とはインターフェースとかだいぶ違います)。
<2012/12/11:改定>
Boost.Coroutineの正式入り版がGithubにあったので、そっち向けの記述に変更。
今回はゲーム利用を前提に置いた試し方をしてみた。
Coroutine(コルーチン)は、簡単に言えば
"関数の途中で処理を中断して関数の外に値を返し、次同じ関数が実行された時に前回中断した処理から再開できる"
という代物(いわゆる"継続"の実装のひとつ)。
ゲーム関連で言えばLuaやXtal、Squirrel等の組み込みスクリプト言語などに標準実装されていて、スクリプト言語を使う理由の半分くらいを占めている機能(主観)。もちろんあと半分は毎度コンパイルしなくていいという点。
これによって、ゲームでの敵の挙動を直観的に記述できてとても良い。
この機能使いたいが為にスクリプトを用いると、結構早いとはいえやはりスクリプト上で動作している関係でC++で記述するより数倍以上遅くなってしまう。
それが、今回のBoost.Coroutineを使えばC++の速度のままコルーチン使えるよね!! というコトでした。
例えば以下のコードだと、右下に速度(6,6)で移動し始めて、20フレーム経ったら左上に速度(-6,-6)で移動する、といった動きがRepeat関数内に直観的に記述できている。
// 白い丸が画面上で行ったり来たりするプログラム #include <boost/coroutine/all.hpp> #include <DxLib.h> class Actor; typedef boost::coroutines::coroutine<int (Actor *)> Routine; void Repeat(Routine::caller_type&); class Actor { public: Actor() : x_(0), y_(0), counter_(0), move_x_(0), move_y_(0),routine_() { } void Update() { if( !routine_.empty() ){ if(counter_-- <= 0){ routine_(this); // 中断される場所まで関数を実行する counter_ = routine_.get(); // 中断された場所で返された値をgetする } } x_ += move_x_; y_ += move_y_; Draw(); } void Draw() { DrawCircle( x_, y_, 40, GetColor(255,255,255),TRUE); } void SetRoutine( std::function<void (Routine::caller_type&)> func ) { routine_ = Routine(func); counter_ = 0; } double GetMoveX() const { return move_x_; } double GetMoveY() const { return move_y_; } void SetMoveX(double move_x){ move_x_ = move_x; } void SetMoveY(double move_y){ move_y_ = move_y; } private: double x_; double y_; double move_x_; double move_y_; int counter_; Routine routine_; }; // 反復ルーチン void Repeat(Routine::caller_type& yield) { const int MOVE_SPEED = 6; yield(9999); // coroutineのコンストラクタで関数が一回実行されるようなので帰ってもらう while( true ){ Actor* actor = yield.get(); // Routineが持つ関数( int (Actor *) )の引数(Actor*)をgetする // 右下に向かう移動量をセット actor->SetMoveX( MOVE_SPEED ); actor->SetMoveY( MOVE_SPEED ); // Routineが持つ関数( int (Actor *) )の返り値(int)を返す yield(20); // 意味的には、関数を停止させたまま20フレームの経過を待つ // 左上に向かう移動量をセット actor->SetMoveX( -MOVE_SPEED ); actor->SetMoveY( -MOVE_SPEED ); yield(20); } } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { ChangeWindowMode( TRUE ) ; SetGraphMode( 600 , 480 , 16 ) ; SetMainWindowText( "Games" ) ; if( DxLib_Init() == -1 ) { return -1;} SetDrawScreen( DX_SCREEN_BACK ) ; Actor actor; actor.SetRoutine( Repeat ); while(ProcessMessage() == 0 && CheckHitKey( KEY_INPUT_ESCAPE ) == 0) { ClearDrawScreen(); actor.Update(); ScreenFlip() ; } DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }
なんでコルーチン用いた書き方が出来ると良いかって、「スクリプトで全体を実装したけど、この処理がアホみたいに重くて動かない…。C++で書きなおして速度上げるか…」ってなった時にそのまま同じものを違う言語で書きなおすだけでいいから。最悪、スクリプト上で完成したら全部C++に機械的に変換できれば超早いよ!! みたいな。若干非現実的だけども。
<追記>
ソースコードにも書いたけども、初回呼び出しが初期化処理として動く的なアレが良く分からない感じ。ひじょーに直観的でない…。manga_osyoさんのこの記事(http://d.hatena.ne.jp/osyo-manga/20121210/1355121438)をコピペしても同じように1回目は戻らないようだし…(乱数ジェネレータなので1回目が無視されても問題なく動くのだが)。
MSVC11が悪いということもあるまいし。
ドキュメント読んでみたりしたが、Rangeイテレータでアクセスしたらちゃんと1回目の呼び出しから動いたりするし、ちょっと分からん感じある。
<追追記>
なんかBoost.Coroutineのリファレンスに乗ってたテストコード動かしたら、出力結果が違って表示されたので、バグの可能性ワンチャンある。MSVC11でしか試してないので、他のコンパイラで試してみたいところ。
<追追追記>
Boost.Coroutineの動きをデバッガで追ってみたところ、どうやらcoroutineのコンストラクタで一度関数が実行されるということのようだ。微妙に直感的でないんだけども、呼び方が悪いんだろうか。
DXライブラリを使った非同期ファイルロード(真)
DXライブラリが公式に非同期読み込みに対応したので、それについての情報を纏めておく。
コレhttp://homepage2.nifty.com/natupaji/DxLib/function/dxfunc_other.html#R21N1
に関する注意事項。
LoadGraph等を実行した時点でのフラグで動作する
SetUseASyncLoadFlag( TRUE );の後に対応関数を呼び出した場合、たとえ途中でSetUseASyncLoadFlag( FALSE );にされた場合であっても非同期読み込みが継続される。
SetUseASyncLoadFlag( TRUE ); // 非同期読み込みフラグON int a = LoadGraph("a.png"); // 非同期読み込み終わるまでは無効なハンドルが返る SetUseASyncLoadFlag( FALSE ); // 非同期読み込みフラグOFF int b = LoadGraph("b.png"); // 同期読み込みなので有効なハンドルが返る
読み込み待ちはメインスレッドで
CheckHandleASyncLoad(...);で読み込み終了までwhile(...)等で待つ場合、おそらくハンドル生成等の部分をメインスレッドのProcessMessage()で行なっているため、
while( CheckHandleASyncLoad(handle) != FALSE ){ ProcessMessage(); Sleep(1); }
のような事をする必要がある。
Direct3D9 はマルチスレッド対応モードで起動すると動作が凄く重くなるので マルチスレッド非対応モードで起動して、SetUseASyncLoadFlag( TRUE ) ; の設定で LoadGraph を行った場合も Direct3D9 の操作が必要な処理だけ メインスレッドの ProcessMessage の中で処理するようにしています なので ProcessMessage を呼ばないと Direct3D9 の操作が必要なところで 処理が止まってしまうというわけです
という事らしい。
2012/04/17追記
struct Loader{ std::map<std::string,int> handle_map; bool Load(std::string filename) { SetUseASyncLoadFlag(TRUE); int handle = LoadGraph( filename.c_str() ); SetUseASyncLoadFlag(FALSE); if( handle == -1){ return false; } handle_map.insert( std::make_pair( filename, handle ) ); return true; } int Get(std::string filename) { auto it = handle_map.find( filename ); // ハンドルがあるなら、ロード終わっているか見てハンドルを返す if(it != handle_map.end() ){ int handle = (*it).second; while( CheckHandleASyncLoad(handle) != FALSE ){ ProcessMessage(); Sleep(1); } return handle; } //ハンドルがないなら、 else{ if( Load(filename) ){ return Get(filename); } else{ return -1; } } } } };
みたいなLoaderクラス介してやれば、事前読み込みしてから使う時にはロード時間なし、もし読み込み終わってないで使おうとしたら同期的な動き、みたいな事が出来て幸せになれるんじゃないですかね。
ところでWindowsでマイクロ秒程度のSleepってどうやるんですかね(血涙