C++ でコルーチンを使ってみます。
僕の心は生粋の(?)Windowsプログラマであり一昔前は CreateFiber でコルーチン的なものを自作していましたが、現在は Boost C++ Libraries にコルーチン機能が含まれているので、それを用いてコルーチンを利用します。
環境
- Windows 7 Home Premium 64bit
- Visual Studio 2013
Boost の入手と設置
Boost C++ Libraries のサイトから Boost のアーカイブを取得します。
今回は(たまたま手元にあったので)boost_1_55_0.zip で。
これを解凍して C:\boost_1_55_0
として設置。
Coroutine 関連のライブラリをビルド
Boost はヘッダのインクルードだけで利用できる機能も多いですが、一部機能はライブラリのビルドが必要になります。今回利用する boost::coroutines もビルドが必要なので、まずビルドを行っていきます。
bjam 生成
VS2013 のコマンドプロンプトを開き、 C:\boost_1_55_0
内で bootstrap.bat を実行。bjam.exe
というビルド用のツールが生成されます。
PATH設定
C:\boost_1_55_0
に PATH を通しておきます。(bjam.exe を利用するため)
必要なモジュールのビルド
coroutine のビルドを行います。併せて、それに依存する context, system のビルドも行っておきます。
> cd C:\boost_1_55_0\libs\coroutine\build
> bjam --toolset=msvc-12.0 link=static,shared threading=multi release debug stage
> cd C:\boost_1_55_0\libs\context\build
> bjam --toolset=msvc-12.0 link=static,shared threading=multi release debug stage
> cd C:\boost_1_55_0\libs\system\build
> bjam --toolset=msvc-12.0 link=static,shared threading=multi release debug stage
以上。これで必要なライブラリがビルドできました。
プログラムからの利用準備
Visual Studio 2013 のプロジェクトの Properties にて。
C/C++ - General - Additional Include Directories に以下を追加設定。
C:\boost_1_55_0
Linker - General - Additional Library Directories に以下を追加設定。
C:\boost_1_55_0\libs\coroutine\build\stage;C:\boost_1_55_0\libs\context\build\stage;C:\boost_1_55_0\libs\system\build\stage
サンプル1:簡単な利用例
yield 値として int 値を用いた簡単なループ。
関数 loop()
に突入した後、(そこのスタック情報を保持したまま)ブロックを抜け、sample1()
のブロックに移動し、int 値の printf を行い、また loop()
に突入し直し、前回の途中からの処理を再開していることがお分かりいただけるだろうか。
#include <stdio.h>
#include <boost/coroutine/coroutine.hpp>
void loop(boost::coroutines::coroutine<int()>::caller_type& yield)
{
printf("loop start.\n");
for (int i = 0; i < 5; ++i) {
yield(i);
}
printf("loop end.\n");
}
void sample1()
{
printf("sample1 start.\n");
boost::coroutines::coroutine<int()> routine(loop);
while (routine) {
int n = routine.get();
printf("n = %d\n", n);
routine();
}
printf("sample1 end.\n");
}
sample1 start.
loop start.
n = 0
n = 1
n = 2
n = 3
n = 4
loop end.
sample1 end.
サンプル2:応用例
個人的な好みとして、クラスをインスタンス化するだけでスレッドやコルーチンが自動稼働しだすモデルが好きなので、今回はそんなクラス Coroutine を作ってみた。Coroutine クラス実装は GitHub リポジトリを参照。
※ここからやや自分世界に入っていて解説が特殊なので注意。分かりやすく書けなくてすみません。折を見て別記事にて解説し直したい。
ゲーム実装で使うことを想定している。(ゲーム実装の文化の背景についてはすみません適当にお察しお願いします。文字数が足りません。)
Coroutine を継承したクラスを定義し、run()
をオーバーライドすることで、そのクラスがインスタンス化された後に自動的に run()
が呼び出される仕組み。run()
の中身は以下のように組まれる想定。
class MyCoroutine : public Coroutine{
void run()
{
hoge(); // 1フレーム目の処理
yield(); // wait
hoge(); // 2フレーム目の処理
yield(); // wait
hoge(); // 3フレーム目の処理
yield(); // wait
// ここまで来るとインスタンスは自殺.
}
};
コルーチンは CoroutineManager
により駆動する。
-
CoroutineManager::instance()->go();
で1フレーム分駆動。 -
while (CoroutineManager::instance()->go()){ sleep(); }
で全コルーチンがすべて終わるまで無限に駆動。
利用コードの完全なコード例は以下。
class MyCoroutine : public Coroutine{
public:
MyCoroutine(std::string name, int cnt){
m_name = name;
m_cnt = cnt;
printf("%s start.\n", m_name.c_str());
}
~MyCoroutine(){
printf("%s end.\n", m_name.c_str());
}
void run(){
for (int i = 0; i < m_cnt; i++){
yield();
printf("%s %d\n", m_name.c_str(), i);
}
}
private:
std::string m_name;
int m_cnt;
};
void sample2()
{
new MyCoroutine("hello", 4);
new MyCoroutine("world", 2);
printf("manager start.\n");
while (CoroutineManager::instance()->go()){
printf("manager sleep\n");
}
printf("manager end.\n");
}
hello start.
world start.
manager start.
manager sleep
hello 0
world 0
manager sleep
hello 1
world 1
manager sleep
hello 2
world end.
manager sleep
hello 3
manager sleep
hello end.
manager end.
この↑解説でしっくり来てくれた奇特な方にとって、以下に続く解説はとてもEasyです。
サンプル3:ラムダ式によりコルーチン処理を定義する
ちょっとした小さい処理をコルーチンで実行したい場合に、そのためだけにひとつクラスを定義するのはちょっと負荷が大きいので、もう少し手軽にコルーチンを駆動させる手段を提供したい。
TinyCoroutine というクラスを作った。このクラスは、コンストラクタにラムダ式を渡すだけでその処理がコルーチン上で実行される仕組みを提供する。
void sample3()
{
new TinyCoroutine([](Coroutine* coroutine){
for (int i = 0; i < 3; i++){
printf("aa %d\n", i);
coroutine->yield();
}
});
new TinyCoroutine([](Coroutine* coroutine){
for (int i = 0; i < 2; i++){
printf("bb %d\n", i);
coroutine->yield();
}
});
printf("manager start.\n");
while (CoroutineManager::instance()->go()){
printf("manager sleep\n");
}
printf("manager end.\n");
}
manager start.
aa 0
bb 0
manager sleep
aa 1
bb 1
manager sleep
aa 2
manager sleep
manager sleep
manager end.
以上、俺俺クラスの紹介になってしまった。反省は少ししている。
サンプルプロジェクト
以下に今回のサンプルプロジェクトを上げました。
https://github.com/kobake/BoostCoroutineSample