Segmentation fault対策

RubyオブジェクトをCのメモリに保持するのは、「そういった参照は推奨されません」とRubyのマニュアルにも書いてあるとおり、あまりよろしくない。
保守的GCがどのようにデータを検索し、マークをつけているのかなど、Ruby内部の動きを理解しないと正しく動かないからだ。
俺もよくわかっていないが、データの持ちかたやメモリの確保方法によってGCが異様に遅くなることはDXRubyの開発を通じて体験している。


DXRubyはWindow.drawの描画メソッドを呼び出しても、その場で描画はしない。
これはフレームスキップを効率的に行うとか、描画順ソートが欲しいとか、そういった理由による。
いったんメモリにためこんであとでまとめて描画する。
そうするためには、Rubyオブジェクトが持つ情報を、Cで確保したメモリに保持する必要がある。
このとき、RubyGCで管理されるメモリにデータを持つと、GCが遅くなる。
GC管理外に持つと、Rubyオブジェクトを束縛しなくなって、Window.drawから描画までの間にImageオブジェクトがGCで回収されてしまう可能性が出る。
さっきアップしたマニュアルのためし書きには、そのへんの注意事項が書いてあったわけだ。
でもやっぱりSegmentation faultは嬉しくない。
なんとかして対策できないだろうか。


正攻法でいくなら、Rubyの配列を作って、描画登録データクラスからオブジェクトを生成して突っ込む、とかになるが、当然おもしろいほど遅くなるだろう。
Imageオブジェクトに参照カウントを持たせるのも手だ。
releaseが呼ばれるたびにカウントが減らされるから、Window.drawしたときにカウントを増やして、描画したときreleaseを呼ぶ。
RubyオブジェクトなのにGCとは別に解放が管理されるのはかなりキワドイが。
要はGCしようとしたときに、描画待ち中のオブジェクトにマークできればいいのだ。
描画待ち中のオブジェクトはCのメモリで持っているから、GCが動いたときにCの関数を呼んでくれる機能があればそこでマークできて解決する。
が、そんなものは見あたらない。
とりあえず暫定対策で、内部処理用にDXRubyInternalMarkクラスを作って、そのオブジェクトを1個だけ生成、大域変数としてGCに登録して、そいつのmark関数から内部保持オブジェクトをすべてマークするようにした。
これでGCを動かしてもSegmentation faultはでなくなった。
たぶん、速度的にもこれが一番速い。
マークしないときと比べればGCがちょっとだけ遅くなるが、しょうがないだろう。


この対策と、Window.drawTileの不具合修正、それから、Fontクラスにフォントファイルからフォントを登録する機能の追加、Windowモジュールに足りないゲッターを追加、などを行って、DXRuby1.0.6としよう。
1.0.5との互換性は確保する方針で。