Hatena::ブログ(Diary)

joynote break;

2012-12-07

[][] Boost.Coroutineが正式に入ったので

Boost.Coroutineが正式にBoost入りしそうなので、Boost Vaultから試作品のBoost.Coroutineを持ってきて試してみた(※正式に入る版とはインターフェースとかだいぶ違います)。

<2012/12/11:改定>

Boost.Coroutineの正式入り版がGithubにあったので、そっち向けの記述に変更。


今回はゲーム利用を前提に置いた試し方をしてみた。

Coroutine(コルーチン)は、簡単に言えば

"関数の途中で処理を中断して関数の外に値を返し、次同じ関数が実行された時に前回中断した処理から再開できる"

という代物(いわゆる"継続"の実装のひとつ)。

ゲーム関連で言えばLuaXtalSquirrel等の組み込みスクリプト言語などに標準実装されていて、スクリプト言語を使う理由の半分くらいを占めている機能(主観)。もちろんあと半分は毎度コンパイルしなくていいという点。

これによって、ゲームでの敵の挙動を直観的に記述できてとても良い。

この機能使いたいが為にスクリプトを用いると、結構早いとはいえやはりスクリプト上で動作している関係で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のコンストラクタで一度関数が実行されるということのようだ。微妙に直感的でないんだけども、呼び方が悪いんだろうか。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

リンク元