ラーメンは味噌汁

それとなくプログラム関係のメモを残していくブログ

mruby @ Fiberを試してみる

FiberはRubyのコルーチン的な機能

どのスクリプトを組み込むか決める時に個人的に気にしているのがコルーチンを使えるかどうかだったりする。ライブラリのサイズや実行速度ももちろん重要ではあるけれども、せっかくなら楽に記述できるにこしたことはない。

Rubyでのコルーチンに相当するものがFiberという機能。mrubyでも使えるようなので少し試してみる。


やりたい事は以下の3つ

1.単純な中断、復帰

毎フレーム順番に処理を実行していく関数をC言語で書こうとするとswitch-caseだらけになってしまう。これをもっと簡単に直線的に書きたい。

処理1
yield // 中断、次のフレームで呼ばれたら続きから

処理2
yield // 中断、次のフレームで呼ばれたら続きから

2.関数呼び出しした先でも中断、復帰

ゲームを作っていて何かを待つ処理というのはかなり多い。アニメーションの終了待ち、会話の終了待ち、サウンドの再生待ち、エフェクトの再生待ち、画面の暗転待ち、単純に間を開けるために10フレーム待ち、などなど例を上げればたくさん出てくる。

これらは至る所に出てくる処理なので毎度毎度同じことは書きたくない。理想は待ち用の関数内で待ち処理が終わるまで帰ってきて欲しくない。

アニメーション再生関数()
アニメーション終了待ち関数() // 再生終了までこの中でyieldし続けて欲しい

// アニメーション再生終了したら次はここから
会話開始関数()
会話終了待ち関数() // 会話が終わるまでこの中でyieldし続けて欲しい

// 会話が終了したら次はここから

3.C言語側からでも制御

mruby側からだけしか処理の中断、復帰できないのならあまり意味がないのでもちろんC言語側からでも制御できるようにしたい。


mruby-fiber指定してmrubyをビルド

mrubyでは拡張機能(mrbgems)はライブラリのコンパイル時に指定して一つのバイナリにする仕組みをとっている。これはファイル読み込みが無い環境へも対応するためらしい。

Fiberを使うためのmruby-fiberもこのmrbgemsに用意されているのでbuild_confib.rbのクロスコンパイラ設定にmruby-fiberを指定する。

conf.gem :core => "mruby-fiber"

iOS用の全体としてはこんな感じ。

MRuby::CrossBuild.new('ios') do |conf|
  toolchain :clang
  
  SDK_PATH = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.1.sdk"
  ARCH = "-arch arm64 -arch armv7 -arch armv7s"

  conf.cc.flags << "#{ARCH}"
  conf.cc.include_paths << "#{SDK_PATH}/usr/include"
  conf.linker.flags << "#{ARCH}"
  conf.linker.library_paths << "#{SDK_PATH}/usr/lib"

  conf.bins = []

  conf.gem :core => "mruby-print"
  conf.gem :core => "mruby-math"
  conf.gem :core => "mruby-enum-ext"
  conf.gem :core => "mruby-fiber"
end

出来上がったlibmruby.aをリンクして準備完了。

実行テスト

通常のRubyと同じ様にFiber.newにブロックを渡してFiber.yieldで中断、Fiber.resumeで処理を復帰できる。違う部分はビルド時に組み込まれているのでFiberをrequireする必要がないことくらい。

# test.rb

# 指定フレーム数だけ待機する関数
def WaitFunc(time)
  time.times{|x|
    puts "wait... " + x.to_s
    Fiber.yield
  }
end

# ファイバー作成
fiber = Fiber.new do
	
  # yieldで処理を中断できる
  puts "update 1"
  Fiber.yield

  puts "update 2"
  Fiber.yield
	
  # 5フレーム待機する
  WaitFunc 5
	
  puts "update end"
end

puts "--- Fiber Test Begein ---"

# ファイバーが生きている間更新する
frame = 0
while fiber.alive? do
  puts ">frame" + frame.to_s
  fiber.resume

  frame += 1
end

puts "--- Fiber Test End ---"

このRubyのコードをiOS上で実行してみる。

#include <mruby.h>
#include <mruby/compile.h>

// mrubyのテスト
static void mruby_test()
{
  NSString* res = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"rb"];
  const char* rubyCode = [[NSString stringWithContentsOfFile:res encoding:NSUTF8StringEncoding error:nil] UTF8String];

  mrb_state* mrb = mrb_open();
  mrb_load_string(mrb, rubyCode);
  mrb_close(mrb);
}

実行結果はこんな感じ。ちゃんと処理が中断、復帰できている。

--- Fiber Test Begein ---
>frame0
update 1
>frame1
update 2
>frame2
wait... 0
>frame3
wait... 1
>frame4
wait... 2
>frame5
wait... 3
>frame6
wait... 4
>frame7
update end
--- Fiber Test End ---

復帰処理をC言語側から行う。

上のテストではスクリプト側で復帰処理を行っていたが、C言語側から制御したい場合もあるので今度はそのテストを。

まず、直にファイバーをC言語側から呼び出しても期待した通りの結果にはならなかった。
とりあえずファイバーを関数でラップして使用することにしてみた。

mruby側にファイバーを作成する関数と復帰させる関数を用意する。

# test.rb

# ファイバー作成
def create_func()
  puts "create_func()"
  
  return Fiber.new {do_something}
end

# ファイバーをリジューム
def do_func(fiber)
  puts "do_func()"
  fiber.resume if fiber.alive?
  
  return fiber.alive?
end

# 指定フレーム数だけ待機する関数
def WaitFunc(time)
  time.times{|x|
    puts "wait... " + x.to_s
    Fiber.yield
  }
end

# 適当な処理
def do_something()
  # yieldで処理を中断できる
  puts "update 1"
  Fiber.yield
  
  puts "update 2"
  Fiber.yield
  
  # 5フレーム待機する
  WaitFunc 5
  
  puts "update end"
end

C言語側からmrubyの処理を制御する。

#include <mruby.h>
#include <mruby/compile.h>

// mrubyのテスト
static void mruby_test()
{
  NSString* res = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"rb"];
  const char* rubyCode = [[NSString stringWithContentsOfFile:res encoding:NSUTF8StringEncoding error:nil] UTF8String];

  mrb_state* mrb = mrb_open();
  mrb_load_string(mrb, rubyCode);

  mrb_sym create_func = mrb_intern_cstr(mrb, "create_func");
  mrb_sym do_func = mrb_intern_cstr(mrb, "do_func");

  mrb_value top_self = mrb_top_self(mrb);

  mrb_value fiber = mrb_funcall_argv(mrb, top_self, create_func, 0, NULL);
  while (true)
  {
    mrb_value alive = mrb_funcall_argv(mrb, top_self, do_func, 1, &fiber);
    if (!mrb_test(alive))
    {
      break;
    }      
  }
  mrb_close(mrb);
}

実行結果は期待通り。

create_func()
do_func()
update 1
do_func()
update 2
do_func()
wait... 0
do_func()
wait... 1
do_func()
wait... 2
do_func()
wait... 3
do_func()
wait... 4
do_func()
update end

まつもとゆきひろ直伝 組込Ruby「mruby」のすべて 総集編

まつもとゆきひろ直伝 組込Ruby「mruby」のすべて 総集編

たのしいRuby 第3版

たのしいRuby 第3版

  • 作者: 高橋征義,後藤裕蔵,まつもとゆきひろ
  • 出版社/メーカー: ソフトバンククリエイティブ
  • 発売日: 2010/03/31
  • メディア: 単行本
  • 購入: 15人 クリック: 394回
  • この商品を含むブログ (81件) を見る