ページ 1 / 1
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がそれに相当する)
さらに、指定回数の繰り返しとか、無限に繰り返すとかも作ってみたので、参考までにどうぞ
► スポイラーを表示
コード:
#include <DxLib.h>
#include <vector>
#include <functional>
////
// std::function<bool ()> をタスクとし、戻り値は、タスクが継続中かどうかを示す真偽値である
// タスクは、falseを返した後でも、繰り返し呼び出し可能でなければならない
////
// タスクfをn回繰り返すタスク
class limited_loop_coroutine
{
public:
limited_loop_coroutine(int n, std::function<bool ()> f)
: m_count(0)
, m_limit(n)
, m_task(f)
{
if (n <= 0) {
throw std::invalid_argument("nは正の整数である必要があります");
}
if (!f) {
throw std::invalid_argument("タスクが空です");
}
}
// 繰り返し回数に達したら、falseを返す(カウントは0にリセットされる)
bool operator()()
{
if (!m_task()) {
m_count = (m_count + 1) % m_limit;
return m_count != 0;
}
return true;
}
private:
int m_count;
int m_limit;
std::function<bool ()> m_task;
};
// タスク列f[i]を順番に実行するタスク
class sequential_coroutine
{
public:
sequential_coroutine(std::vector<std::function<bool ()>> s)
: m_index(0)
, m_sequence(s)
{
if (s.empty()) {
throw std::invalid_argument("シーケンスが空です");
}
}
// f[i]をすべて実行したら、falseを返す(添字は0にリセットされる)
bool operator()()
{
if (!m_sequence[m_index]()) {
m_index = (m_index + 1) % m_sequence.size();
return m_index != 0;
}
return true;
}
private:
int m_index;
std::vector<std::function<bool ()>> m_sequence;
};
////
// タスクを作成するヘルパー関数
////
// タスクfをずっと繰り返すタスク
std::function<bool ()> loop(std::function<bool ()> f)
{
return [=]() -> bool {
f();
return true;
};
}
// タスクfをずっと繰り返すタスク(戻り値なしの関数からでも作れるように)
std::function<bool ()> loop(std::function<void ()> f)
{
return [=]() -> bool {
f();
return true;
};
}
// fを一回だけ実行するタスク
std::function<bool ()> one_time(std::function<void ()> f)
{
return [=]() -> bool {
f();
return false;
};
}
// タスクfをn回繰り返すタスク
std::function<bool ()> repeat(int n, std::function<bool ()> f)
{
return limited_loop_coroutine(n, f);
}
// タスクシーケンスを生成する
std::function<bool ()> sequence(std::vector<std::function<bool ()>> s)
{
return sequential_coroutine(s);
}
std::function<bool ()> sequence(std::function<bool ()> f1)
{
std::vector<std::function<bool ()>> s;
s.push_back(f1);
return sequence(s);
}
std::function<bool ()> sequence(std::function<bool ()> f1, std::function<bool ()> f2)
{
std::vector<std::function<bool ()>> s;
s.push_back(f1);
s.push_back(f2);
return sequence(s);
}
std::function<bool ()> sequence(std::function<bool ()> f1, std::function<bool ()> f2, std::function<bool ()> f3)
{
std::vector<std::function<bool ()>> s;
s.push_back(f1);
s.push_back(f2);
s.push_back(f3);
return sequence(s);
}
std::function<bool ()> sequence(std::function<bool ()> f1, std::function<bool ()> f2, std::function<bool ()> f3, std::function<bool ()> f4)
{
std::vector<std::function<bool ()>> s;
s.push_back(f1);
s.push_back(f2);
s.push_back(f3);
s.push_back(f4);
return sequence(s);
}
////
// 必要な数だけ、オーバーロードする
////
// テスト用クラス
class test_object
{
public:
test_object(int x, int y, int color)
: m_x(x)
, m_y(y)
, m_color(color)
{
}
void move(int vx, int vy)
{
m_x += vx;
m_y += vy;
}
void set_task(std::function<bool ()> task)
{
m_task = task;
}
void update()
{
// 設定されたタスクを実行する
// タスクがfalseを返したら、空のタスクに設定する
if (m_task && !m_task()) {
m_task = std::function<bool ()>();
}
}
void draw() const
{
DrawCircle(m_x, m_y, 20, m_color);
}
private:
int m_x;
int m_y;
int m_color;
std::function<bool ()> m_task;
};
int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
ChangeWindowMode(TRUE);
if (DxLib_Init() != 0) {
return -1;
}
SetDrawScreen(DX_SCREEN_BACK);
test_object t[2] = {
test_object(200, 200, GetColor(255, 0, 0)),
test_object(300, 300, GetColor(0, 255, 255))
};
//「→5x20, ↓5x20, ←5x20, ↑5x20」を繰り返す
t[0].set_task(
loop(
sequence(
repeat(20, one_time([&]{t[0].move(5, 0);})),
repeat(20, one_time([&]{t[0].move(0, 5);})),
repeat(20, one_time([&]{t[0].move(-5, 0);})),
repeat(20, one_time([&]{t[0].move(0, -5);}))
)
)
);
// 左右に振動しつつ、徐々に左に移動
t[1].set_task(
loop(
sequence(
repeat(5, one_time([&]{t[1].move(10, 0);})),
repeat(5, one_time([&]{t[1].move(-11, 0);}))
)
)
);
while (ProcessMessage() == 0) {
if (CheckHitKey(KEY_INPUT_ESCAPE)) {
break;
}
ClearDrawScreen();
for (int i = 0; i < 2; ++i) {
t[i].update();
t[i].draw();
}
ScreenFlip();
}
DxLib_End();
return 0;
}
Re: c++で弾幕風における”yield”の実現
Posted: 2011年9月28日(水) 03:34
by 御津凪
弾幕風の "yield" に当たるコルーチン(マイクロスレッドとも呼ばれる)の処理は Win32API にファイバーと呼ばれる機構で、ある程度同じように実装可能ですよ。
※以下、ファイバー機構を使う際の説明です
► スポイラーを表示
ファイバー機構は明示的に指定したファイバー(上記のコルーチンやマイクロスレッドの処理と同じ意味)へスイッチします。
(つまり、元の実行部分に戻るにはそのファイバースレッドを指してスイッチ処理する必要がある)
弾幕風のマイクロスレッドの様な処理にするには、以下の様な感じになります。
(混乱を防ぐため、マイクロスレッドで名前を統一します)
- マイクロスレッドを作成する(弾幕風でいう弾生成に相当)
- 実行ループ内で各マイクロスレッドに対してスイッチする処理を行う
- マイクロスレッド内から実行ループを処理しているメインスレッドへスイッチする(yield 命令に相当)
- マイクロスレッドから戻ってきたら次のマイクロスレッドへスイッチする
上記のように行えば "yield" の実現が可能です。(実装はちょっとややこしいで注意)
※コード書く時間なかったので要望あったら書きます
他の方法としては、自分でコルーチン機能をもつスクリプトを構築するとかがありますね。
オフトピック
ちなみに、私の開発中ライブラリに、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を継承した関数オブジェクトを作るという方法もありますね。
メンバ関数っぽく呼び出せますし。