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の簡単さを加速するのがよいんじゃないかと思う。