Fiberを利用してみる

そんなことを考える人は当然のように既にいるわけだが。
yhara氏のサイトroute477より。
http://route477.net/d/?date=20080210#p01


FiberはRuby1.9で追加されたクラスで、一般的にはコルーチンと呼ばれる。ゲーム関係ではマイクロスレッド。
これを使うと、実行中のメソッドの状態を残したまま上に戻ることができる。状態というのはローカル変数やプログラムポインタなどのことで、スレッドが切り替えられたような状態ということだ。プログラマの意思で好きなタイミングでスレッドアウトすることができる。
シンプルな例を挙げるなら

a = Fiber.new do # Fiberオブジェクトの生成
  i = 0
  loop do
    Fiber.yield i # ここでいったん戻る
    i += 1
  end
end

loop do
  p a.resume #=> 0から順に表示される
end

って感じだ。


これをゲームに活用すると何ができるかというと、通常の書き方だと
・各オブジェクトの移動メソッド内で状態をチェックしつつ移動処理を分岐しなければいけない
というのが、
・単純に順番に並べて、フレームを進めるところでFiber.yieldを呼ぶ
となる。
このあたりの具体的な話は弾幕Wikiさまの解説がわかりやすい。
http://f27.aaa.livedoor.jp/~thdmhul/pukiwiki/pukiwiki.php?%A5%DE%A5%A4%A5%AF%A5%ED%A5%B9%A5%EC%A5%C3%A5%C9%B9%D6%BA%C2


さて、俺の興味はこれがゲームに使えるか、という話なわけだ。いつもどおり。

require 'dxruby'

class Sikaku
  attr_reader :fiber
  @@image = Image.new(100,100,[255,255,255])

  def initialize(x, y)
    @x = x
    @y = y
    @fiber = Fiber.new do
      loop do
        60.times { @x += 4; Fiber.yield }
        60.times { @y += 4; Fiber.yield }
        60.times { @x -= 4; Fiber.yield }
        60.times { @y -= 4; Fiber.yield }
      end
    end
  end

  def draw
    Window.draw(@x, @y, @@image)
  end
end

class Maru
  attr_reader :fiber
  @@image = Image.new(101,101).circleFill(50,50,50,[255,255,255])

  def initialize(x, y)
    @dx = x
    @dy = y
    @angle = 0
    @fiber = Fiber.new do
      loop do
        @x = Math.sin(Math::PI / 180 * @angle) * 100 + @dx
        @y = Math.cos(Math::PI / 180 * @angle) * 100 + @dy
        @angle += 1
        Fiber.yield
      end
    end
  end

  def draw
    Window.draw(@x, @y, @@image)
  end
end

objects = [Sikaku.new(50, 50), Maru.new(400,250)]

Window.loop do
  objects.each do |obj|
    obj.fiber.resume
    obj.draw
  end
end

ご覧のように記述性は非常に高い。
そしてうちの環境では100個動かすぶんにはなかなか速い。
ところが、150個ほどになると急に遅くなる。GC負荷が異様に高いように見える。
yhara氏は300個動くって書いてたのにな。Athlon64X2 1.7GHzなのに300個動かすと負荷率300%になってしまう。GC.disableすると負荷60%ぐらいになるから、このへんに秘密がありそうだ。
それさえ解決すればかなり使えると思う。
俺がよく考えている「Cによるゲームアクセラレート」に使えるテクニックではないが、そんなものは弾幕ゲーぐらいにしか使い道は無く、普通のゲームを作るならこういうのを有効に使ってRubyの簡単さを加速するのがよいんじゃないかと思う。