ページ 11

c++で弾幕風における”yield”の実現

Posted: 2011年9月26日(月) 14:22
by Sigma
タイトル通り弾幕風のようなyieldで処理を止めて別の処理に移り、
次のループで自分に処理順がきたときに処理の続きをするといったタスクシステムを作りたいのですが、
組み方の検討がつかずに困っています。

一応listでタスクを格納するコンテナを作って処理させようとしています。
タスクを作る、全タスクの更新と描画、いらないタスクの消去の仕組みはできるのですが、
途中で別タスクの処理に移る処理でつまずいてます。

自分的にはカウンタとswitchをつかうような記述はしたくないです。

コード:

void task::update()
{
    switch(mCount)
    {
    case 0:
        /*1ループ目の処理*/
        break;
    case 1:
        /*2ループ目の処理*/
        break;
    //以下略
    }
    mCount++;
}
↑こういう感じではなく弾幕風のような

コード:

void task::update()
{
    /*1ループ目の処理*/
    yield;  //ここで別タスクに移動して次のループまで処理しない
    /*2ループ目の処理*/
    wait(10);  //10フレーム待機してはじめに戻る
}
void wait(int w)
{
    for(int i=0; i<w; i++) yield;
}
みたいな処理をしたいです。

頻繁に申し訳ないですがよろしくお願いいたします。

Re: c++で弾幕風における”yield”の実現

Posted: 2011年9月26日(月) 17:07
by softya(ソフト屋)
コルーチンみたいな処理ですね。
boostでこんなのならあるみたいです(使った事無いです)。
「[C++]Boost.Coroutine」
http://blogs.wankuma.com/melt/archive/2 ... 30217.aspx

Re: c++で弾幕風における”yield”の実現

Posted: 2011年9月26日(月) 22:34
by めるぽん
Boost.Coroutine は正式に Boost の仲間入りしたわけではないです。どちらかというと、今は Boost.Fiber の方が注目されていますね。
更に、コルーチンの要となるコンテキストスイッチの部分だけまずは Boost 入りさせようという動きになっていて、今後どうなるかは分からないです。
まあ正式に入っていないというだけで、十分実用にはなるので、使ってもいいとは思います。

あとは Boost.Asio の作者が作った、マクロによるコルーチンの実装を使うという方法もあります。
http://d.hatena.ne.jp/faith_and_brave/2 ... 1286432629

Re: c++で弾幕風における”yield”の実現

Posted: 2011年9月27日(火) 17:14
by Sigma
分かりやすそうなのでめるぽんさんの案をやってみようと思いますが、
この方法って

コード:

class task : coroutine
{
    task():
    i(0)
    {}
    void update()
    {
        reenter(this) {
            i++;
            yield return;
            i *= i;
            yield return;
        }
    }
private:
    int i;
}
コレでメンバ関数のupdateを実行するようにしてもちゃんと中途開始ができますか?
あと下まで行ったときに次にupdateを実行するときにはじめに戻りますでしょうか?

Re: c++で弾幕風における”yield”の実現

Posted: 2011年9月27日(火) 17:19
by softya(ソフト屋)
試したほうが速いと思いますが、試せない環境なのでしょうか?

Re: c++で弾幕風における”yield”の実現

Posted: 2011年9月27日(火) 22:20
by a5ua
std::function(もしくは、boost::function)を使って、1フレームの処理をvectorとかに入れておいて、
updateで順番に呼び出せば、それっぽいことはできそうな気がする。(以下に示すコードは、sequential_coroutineがそれに相当する)

さらに、指定回数の繰り返しとか、無限に繰り返すとかも作ってみたので、参考までにどうぞ
► スポイラーを表示

Re: c++で弾幕風における”yield”の実現

Posted: 2011年9月28日(水) 03:34
by 御津凪
弾幕風の "yield" に当たるコルーチン(マイクロスレッドとも呼ばれる)の処理は Win32API にファイバーと呼ばれる機構で、ある程度同じように実装可能ですよ。

※以下、ファイバー機構を使う際の説明です
► スポイラーを表示
他の方法としては、自分でコルーチン機能をもつスクリプトを構築するとかがありますね。
オフトピック
ちなみに、私の開発中ライブラリに、C++ネイティブで"yield"に相当する機能を提供するコルーチンライブラリ"Coro"を同梱してたりしてます。
(開発版として公開している中に同梱してますが、お勧めはしません)

Re: c++で弾幕風における”yield”の実現

Posted: 2011年9月28日(水) 11:13
by Sigma
今boostを導入していますが、
何故か正しくパスも通したはずなのに"yield.hpp"がないと言われてしまってます…

ファイバーについて調べたらこっちも弾幕風のyieldと近い感覚でできそうですが、
クラスに対応できるのかという心配があります。

Re: c++で弾幕風における”yield”の実現

Posted: 2011年9月28日(水) 12:01
by めるぽん
Sigma さんが書きました:今boostを導入していますが、
何故か正しくパスも通したはずなのに"yield.hpp"がないと言われてしまってます…
URL の内容をちゃんと読んでください。yield.hpp は、Boost.Asio の example として存在しているだけです。
Boost の include へパスを通しても無意味なので、example からファイルをローカルにコピーするなりして使ってください。
Sigma さんが書きました:ファイバーについて調べたらこっちも弾幕風のyieldと近い感覚でできそうですが、
クラスに対応できるのかという心配があります。
Boost.Coroutine, Boost.Fiber は(Windows 上では)Win32API のファイバー関数を利用しているので、それを参考にすればいいと思います。
ただ Boost.Coroutine, Boost.Fiber を使った方が断然楽だとは思いますが。

Re: c++で弾幕風における”yield”の実現

Posted: 2012年3月05日(月) 16:31
by Sigma
長らく放置してしまって申し訳ありません。
yieldの実装について試行錯誤をしてましたが、擬似コルーチンの手法を見つけ今実装中です。
http://d.hatena.ne.jp/izmktr/20120220/1329745486#c

しかし試しては見たものの、「error C2051: case 式は、整数型定数でなければなりません。」と言われてしまいました。
試しにswitch文のcaseに__LINE__を入れただけのものを実行してみると同じエラーが吐かれました。
もしかして環境によってcaseに__LINE__が使えないのでしょうか…

Re: c++で弾幕風における”yield”の実現

Posted: 2012年3月05日(月) 18:05
by softya(ソフト屋)
少なくともリンク先のコードはVC++2008ではエラーにはなりません。
開発環境とコードをを明示してもらった方が良いかもしれません。

Re: c++で弾幕風における”yield”の実現

Posted: 2012年3月05日(月) 18:28
by Sigma
環境はVC++2010Expressです。
DXLibを参照させてあります。

コード:

//先ほどのリンク先にあるコルーチンクラスを継承させたクラスの1関数
void EffNowLoading::UpdateTask()
{
	CoroutineBegin();
	for(int i=0; i<64; i++){
		if(mMoveB>544)mMoveB -= 64/15;
		yield;	//この行で先ほどのエラーが起きてしまう
	}
	for(int i=0; i<64; i++){
		mBrinkA += 360/45;
		mBrinkB += 360/60;
		yield;
	}
	CoroutineEnd();
	return;
}

Re: c++で弾幕風における”yield”の実現

Posted: 2012年3月05日(月) 18:48
by softya(ソフト屋)
とりあえずコンパイルが通るものを書いて見ましたが、forのiの値が保証されないのでどんな動作をするかは保証出来ないと思います。
元のサンプルのクラスのメンバ変数にiがあるのは意味がある事なのでよく考えてみて下さい。

コード:

#include <stdio.h>

#ifndef CoroutineHeader
#define CoroutineHeader

class Coroutine
{
protected:
	int state;
public:
	Coroutine(): state( 0 ) {}
	virtual ~Coroutine() {}
};

#define CoroutineBegin() switch(state){case 0:
#define CoroutineEnd()   default: break;}
#define yield {state = __LINE__; return false; case __LINE__:;}

#endif


class Test: public Coroutine
{
	int i;

public:
	bool Foo() {
		CoroutineBegin();
		for ( i = 1; i <= 20; i++ ) {
			printf( "%d ", i );
			yield;
			if ( ( i % 3 ) == 0 ) {
				printf( "fizz " );
				yield;
			}
			if ( ( i % 5 ) == 0 ) {
				printf( "buzz " );
				yield;
			}

		}
		CoroutineEnd();
		return true;

	}

	int mMoveB;
	int mBrinkA;
	int mBrinkB;
	bool UpdateTask()
	{
		int i;
		CoroutineBegin();
		    for(i=0; i<64; i++){
		        if(mMoveB>544)mMoveB -= 64/15;
				yield;
		    }
		    for(i=0; i<64; i++){
		        mBrinkA += 360/45;
		        mBrinkB += 360/60;
				yield;
		    }
		CoroutineEnd();
	    return true;
	}
};

int main( void )
{
	Test test;
	for ( ;; ) {
		bool finish = test.Foo();
		if ( finish ) break;
		printf( "---\n" );

	}

	return 0;
}


Re: c++で弾幕風における”yield”の実現

Posted: 2012年3月05日(月) 20:43
by Sigma
もしかしてと思い、コルーチンの定義・コルーチンを使用する関数を同じファイルにした所通りました。
これってファイル分割できないのですね・・・
となると割と不便。。。

Re: c++で弾幕風における”yield”の実現

Posted: 2012年3月09日(金) 01:38
by Sigma
一応通ったコードを載せます。

コード:

#ifndef _EFFNOWLOADING_H_
#define _EFFNOWLOADING_H_

#include "DxLib.h"
#include "../Effect/FallParticle.h"
#include "../Utility/Coroutine.h"
#include "../Loader/LoadData.h"
#include <list>
using namespace std;

#define CoroutineBegin switch(state){case 0:
#define CoroutineEnd   default: break;}
#define yield {state = __LINE__; return; case __LINE__:;}

class EffNowLoading : public Coroutine
{
public:

//--------------------------------------------------------------------------------------------------------
//            省略
//--------------------------------------------------------------------------------------------------------

	//=======================================================================//
	/**
 	 * 更新タスク
 	 */
	//=======================================================================//
	void UpdateTask(){
		CoroutineBegin;
		loop(15){
			if(mMoveB>544)mMoveB -= 64/15;
			yield;
		}
		loop(300){
			mBrinkA += 360/45;
			mBrinkB += 360/60;
			yield;
		}
		CoroutineEnd;
		return;
	}

private:
	//文字画像描画
	Primitive2D mPrimA;
	Primitive2D mPrimB;
	//モーション制御用
	float	mMoveB;
	float	mBrinkA;
	float	mBrinkB;
	//文字画像格納用
	int	mImgHnd;

	//ブロックコンテナ
	list< FallParticle* > mPtcContainer;
	//ブロックイテレーター
	list< FallParticle* >::iterator mItr;
};

#endif
どうやら

コード:

#define CoroutineBegin switch(state){case 0:
#define CoroutineEnd   default: break;}
#define yield {state = __LINE__; return; case __LINE__:;}

コード:

	void UpdateTask(){
		CoroutineBegin;
		loop(15){
			if(mMoveB>544)mMoveB -= 64/15;
			yield;
		}
		loop(300){
			mBrinkA += 360/45;
			mBrinkB += 360/60;
			yield;
		}
		CoroutineEnd;
		return;
	}
この2つが同じファイルに居ないとエラーを起こすみたいです。
さらにファイル分割でcppに記述しても上記2つが同じファイルにあってもエラーを吐きます。

Re: c++で弾幕風における”yield”の実現

Posted: 2012年3月09日(金) 09:02
by softya(ソフト屋)
ファイルを分離してみましたがコンパイル通りますよ。

CoroutineHeader.h

コード:

#ifndef CoroutineHeader
#define CoroutineHeader
 
class Coroutine
{
protected:
    int state;
public:
    Coroutine(): state( 0 ) {}
    virtual ~Coroutine() {}
};
 
#define CoroutineBegin() switch(state){case 0:
#define CoroutineEnd()   default: break;}
#define yield {state = __LINE__; return false; case __LINE__:;}
 
#endif
main.cpp

コード:

#include <stdio.h>

#include "CoroutineHeader.h"
 
class Test: public Coroutine
{
    int i;
 
public:
    bool Foo() {
        CoroutineBegin();
        for ( i = 1; i <= 20; i++ ) {
            printf( "%d ", i );
            yield;
            if ( ( i % 3 ) == 0 ) {
                printf( "fizz " );
                yield;
            }
            if ( ( i % 5 ) == 0 ) {
                printf( "buzz " );
                yield;
            }
 
        }
        CoroutineEnd();
        return true;
 
    }
 
    int mMoveB;
    int mBrinkA;
    int mBrinkB;
    bool UpdateTask()
    {
        int i;
        CoroutineBegin();
            for(i=0; i<64; i++){
                if(mMoveB>544)mMoveB -= 64/15;
                yield;
            }
            for(i=0; i<64; i++){
                mBrinkA += 360/45;
                mBrinkB += 360/60;
                yield;
            }
        CoroutineEnd();
        return true;
    }
};
 
int main( void )
{
    Test test;
    for ( ;; ) {
        bool finish = test.Foo();
        if ( finish ) break;
        printf( "---\n" );
 
    }
 
    return 0;
}

Re: c++で弾幕風における”yield”の実現

Posted: 2012年3月09日(金) 18:52
by ISLe
余計なことかもしれませんけど…。

Coroutineクラスを継承したクラスでメンバ関数ひとつしかコルーチンを実装できないのでは。
Fooメンバ関数とUpdateTaskメンバ関数を交互に呼ぶと誤動作しますよね。
交互に呼ばないなら良いですけど。

Re: c++で弾幕風における”yield”の実現

Posted: 2012年3月09日(金) 19:25
by Sigma
やってみましたが確かに通りました。
どうやらyieldを使う関数の宣言と定義を別ファイルにするとエラーをはかれるようです。

複数起動できないという点は今気づきました…
先ほどの方法を改良すればいいらしいですが、
私的にはコルーチンを通すための値を作りたくなくて、
あくまで(例えば)taskと関数に宣言するくらいで出来るようにしたいです。
今まで調べてたものもそれが一番のネックで実装を躊躇してましたが、これしか無いのであれば諦めは付くのですが…

Re: c++で弾幕風における”yield”の実現

Posted: 2012年3月10日(土) 15:56
by softya(ソフト屋)
コルーチン・マクロ見る限り継承で作らずにコルーチン・マクロを改造してコルーチンのstateを管理するクラスのインスタンスを指定できるように作れば良いように感じます。
コルーチンが必要な数だけstateを管理するクラスのインスタンスを作ってはダメなのでしょうか?

Re: c++で弾幕風における”yield”の実現

Posted: 2012年3月10日(土) 17:06
by ISLe
実装したいコルーチンの数だけCoroutineを継承した関数オブジェクトを作るという方法もありますね。
メンバ関数っぽく呼び出せますし。