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()->"が凄く汚くて嫌なので、たぶん実際にゲーム用のコード書くときはもっとマシな方法模索すると思いますが…。


まあ、そんな感じで一応動くんじゃないでしょうか!