破棄漏れ
破棄漏れ。それは、その言葉が発せられた途端に実装者を凍りつかせる呪いの言葉。
Starling に限らず、破棄漏れというのは恐ろしいものです。場合によってはあっという間にリソース不足エラーが起きます。どんなに気をつけていても、気付けば、Adobe Scout の使用 GPU メモリの数値が減るどころかどんどん増えていくのを目の当たりにすることに・・・。
大体そういう時は、結構組んでしまっていて、どこが破棄されてないかもう判らない状態に・・・。しかもこれが大き目の開発で、且つ、複数人での開発だったときには・・・。
こうなるともう大量のクラスを細かく見て、細々と破棄処理を実装していくしかないという、雲をつかむような作業行うしかないという地獄絵図となります。
経験ある方は、この時点で血の気の引くような思いをしてるのではないでしょうか。(経験者談
管理クラス
どうするかっていう話ですが、まぁこういうのはもう管理用のクラス作って、そこからしか生成しないことによって生成を管理するしかないです。どっかのタイミングでまとめて破棄とか。何かの条件にあったものを破棄とか。
そんなこといったって
最初からそういう風に気をつけていれば良いですが、ある程度作った後で「やっぱ管理が必要」とかになると、そこから全部のテクスチャ生成を管理クラスからに移行するっていうのは大変ですよね。
あと、コンテンツの都合上、細々ときちんと破棄をしなければならない場合とか、結局自身で気をつけて破棄処理をしていないと・・・となると、管理ミスが発生したりするわけですね。しょうがない、人間ですもの。
最終手段
Starling の中に手を入れちゃいましょう。テクスチャの生成を全て Starling 経由で行っている限りは、Starling で管理できます。
Starling でのテクスチャ生成は、全て Texture クラスの静的メソッドで生成されます。ここで「生成ログ」を仕込んでおくことによって、ある程度「テクスチャがどこで生成されたか」を知ることができるので、もし破棄漏れが起こったとしてもそのログを参照して破棄漏れの箇所を見つけることができます。
(むしろ、この機能がなんでデフォルトで入ってないんだという気がしなくも無いですが)
肝心の手法は?
業務で作ったものなんで、勝手にクラスファイルを載せる訳には行かないんですよねー・・・。というわけで、「ある程度」手法をお伝えするので各自頑張って作ってみてください。
注意
- この手法は Starling 1.7 での手法です。その他のバージョンは各自で調査した上で実装してください。
- あと、動作を保障するものではないので、自己責任で。
- 間違って本家にコミットしたりしないように(出来るか判らないけど
必要なクラス
自分の場合は以下のような形で作りました。
- ログデータ管理クラス(以下のメソッドは静的です)
- setLog(createdTexture:Texture):void
- unsetLog(disposedTexture:Texture):void
- 取得なり出力したりするメソッド
- ログデータクラス
- texture:Texture
- stackTrace:String
実装
Texture クラスの以下の生成部分で、「ログデータ管理クラス」の setLog() を叩きます。管理用のキーとして、生成されたテクスチャも this で渡してやりましょう。
- fromData()
- fromEmbeddedAsset()
- fromBitmapData()
- fromAtfData()
- fromVideoAttachment()
- fromColor()
- empty()
次に setLog() でログデータを生成し、保持します。
- setLog() の中で new Error().getStackTrace() を実行すると、この段階でのスタックトレースが取れます。デバッグでのエラーでよく見る「どこでエラーが出てるよ」っていうのがわかるやつもこれですね。つまり、どこからこのメソッドが呼ばれてるかが判るということです。ということは、どこで生成してるかが判るということですね。
- あとは、このスタックトレースをデータとして保持し、「ログデータ管理クラス」で管理します。
- 更にテクスチャの参照を保持しておくと、後で「破棄されていないテクスチャ」そのものを参照することができますので、破棄漏れをテクスチャ自体を参照することが出来ます。ビジュアル的に確認したい場合に有効です。
Texture クラスの以下の破棄部分で、「ログデータ管理クラス」の unsetLog() を叩きます。どのテクスチャを破棄しているのかを渡さないといけないので、これも this を渡しましょう。
- dispose()
unsetLog() でログデータを削除します。ちゃんと dispose を呼ばれていれば、ログデータも削除されるはずです。
あとは、それらを取得するか出力するメソッドを作って現在生成されているログを参照するだけです。
これで OK(多分
後はただただ、破棄漏れの場所をログで見つけ、それを破棄するようにして、Adobe Scout でちゃんと破棄出来てるか確認して安心しましょう。雲をつかむような破棄チェック地獄とはこれでおさらばです。
希望
本家が機能実装してくれないかな・・・。