RGSS2を知る(2)

前回のんはあまりにしょんぼりだったので、もうひとつ行ってみよう。


RGSS2にはViewportクラスというものがある。
描画範囲を指定するためのクラスだが、Spriteを作るときに指定しないと全画面が描画範囲になる。
全画面範囲のデフォルトViewportとかあるのだろうか。

s = Sprite.new
p s.viewport

結果はnil
viewportを指定しないSpriteは、どのViewportにも属さない、ということだ。


では次にSpriteオブジェクトを束縛するのは誰か。
newしただけで画面に描画されるSpriteは、initialize(もしくはallocate)の中で自分自身の参照をどこかに保存しないといけない。
Graphics.updateがSpriteオブジェクトを取得するために絶対必要な処理だ。
ただ、RGSS2はCなりC++なりで書かれた拡張ライブラリである。
RubyのSpriteオブジェクトを束縛しているのか、それともSpriteの内部情報へのポインタを持っているのか。
それによって、このようなコードの動作結果が変わる。

bitmap = Bitmap.new(100,100)
bitmap.fill_rect(bitmap.rect, Color.new(255,255,255,255))

loop do
  Input.update
  break if Input.trigger?(Input::C)
end

a = Array.new(10000) do
  s = Sprite.new
  s.bitmap = bitmap
  s
end

loop do
  Input.update
  Graphics.update
  if Input.trigger?(Input::C)
    a=nil
    GC.start
  end
end

ボタンを押すと大量のSpriteオブジェクトを生成して描画、もう一度押すとすべての参照を切ってGCする。
これを実行すると、Game.exeのメモリ使用量ははじめ16Mほどだったのが、ボタンを押すと31Mほどに膨れ上がる。
このとき、画面の左上には白い四角が描画される。
そして、もう一度押すと、時間をかけてメモリが減り始める。GCがSpriteオブジェクトを回収しているのだ。
回収が終わっても白い四角は残っていた。これについては後で。
ここから想像できる内部実装は、Sprite.newでSpriteオブジェクトを生成するが、RubyのSpriteオブジェクトを保持せず、内部構造体へのポインタをmallocしたメモリにでも格納している、ということだ。
だからRubyコードからの参照がなくなるとGCに回収される。
おそらくrelease関数で内部保持のポインタを削除するようになっているのだろう。
このような実装はDXRubyでもやっていて、Window.draw時にmallocしたメモリに描画情報を保存している。こっちは描画情報はメソッドで指定するからメモリにコピーしているが、RGSS2の場合はオブジェクトの構造体のポインタを保存して、Grahics.update時にリアルタイムにデータを参照・取得しているはずだ。

bitmap = Bitmap.new(100,100)
bitmap.fill_rect(bitmap.rect, Color.new(255,255,255,255))

Sprite.new.bitmap = bitmap

loop do
  Input.update
  Graphics.update
  if Input.trigger?(Input::C)
    GC.start
  end
end

こちらはnewしっぱのSprite。
ボタンを押すと消えるかと思ったら、消えずに画面に残ったままだった。
さっきのでもそうだったが、これが消えないのはおそらく、描画するものがひとつも無いときに、DirectX9Deviceのbigen・endを呼び出さずにpresentしているのだろう。
これをすると、プライマリサーフェイスの内容を変更せずに画面を更新できる。DXRubyでもやっているが、RGSS2でやる理由は無い気もする。
ちなみに

bitmap = Bitmap.new(100,100)
bitmap.fill_rect(bitmap.rect, Color.new(255,255,255,255))

Sprite.new.bitmap = bitmap
a = Sprite.new
a.bitmap = bitmap
a.x = 200

loop do
  Input.update
  Graphics.update
  if Input.trigger?(Input::C)
    GC.start
  end
end

このようにするときちんと左側の四角は消える。
なので、SpriteオブジェクトはRubyの変数なり配列なりできちんと束縛しておかないと、とりあえず描画されているように見えてもGCが動いたとき(つまり突然)に消えてしまうので注意が必要だ。