#1. 課題
本記事では「Widgetとリソースを切り離す方法」を説明しており、この記事を読むと"Widgetを使用してUIを構築する際の問題とその解消方法"が理解できるようになります。
Widgetを使用してUIを構築する際のありがちな問題点として以下のようなものがあります。
① UIを表示する瞬間にヒッチ(スパイク)が発生する
② UIを表示する際に長いロードがある
③ UIを使用しているシーンのメモリ使用量が多い、リソースが開放されない
これらの問題がある場合は本対策が有用なことがあります。
#2. 解説
対策を記載する前にこれらの原因を把握するためにここで解説します。わかりやすくするために以下に例で示します。
まず①、②に関しては同じ事が原因のケースが多いです。例えば、以下のように1つのWidgetに1つのImage(Texture)を配置している状況があるとします。
この場合はDesigner上に配置したリソースとは**ハードリファレンス(強い参照)**を持つことになります。そのため大量の表示物を一度に表示しようとする場合は、初回はリソースのロードでヒッチ/スパイクが発生する可能性があります。表示したいリソースを自動的に引っ張ってきてロード出来るという便利な側面もありますが、逆に集中するとロード時間が長くなる事もあるので注意が必要です。
次に「このWidgetをある一定の時間だけ表示したい」としましょう。おそらく「このWidgetを子Widgetとして親Widgetから表示するタイミングを制御すること」を想像するかもしれません。具体例として、親Widgetから必要なタイミングで生成する例を以下に挙げてみました。
このようなケースにおいて親Widgetと子Widgetとリソースの関連性は以下のようになります。
図を見るとわかるようにいずれもハードリファレンスとなっています。この時点で想像つくかもしれませんが、親Widget(WB_Main)がロードされた時点で子Widget(WB_Image)も、そのリソースもロードが発生することになります。そのため、表示していないにもかかわらず長いロードが発生している、ということも起きている可能性があります。これは最初にすべてのリソースをロードしているため、子Widget(WB_Image)を表示する際にはロード時間が無いというメリットもありますが、親Widgetのロード時にロード時間が長くなることもあります。
③についてもこれに関連していますが、使い終わって削除したはずの子Widgetがまだメモリ上に残っているという事が発生します。RemoveFromParentでWidgetを削除した後にGCを実行したとしても、ハードリファレンスされたWidgetがある場合はまだ再利用される可能性があるため(オブジェクトが完全に切り離されていない状態)GCの対象とはなりません。これはWidget(Slate)の既定動作であり、親Widgetが完全に削除されたり、OpenLevelなどによるシーンの移動が発生するまではリソースは開放されません。
#3. 対策
これらの問題に対する方法として、完全なレイアウト配置を持たない(動的生成が可能な)Widgetはソフトリファレンスを使用して動的に生成することが挙げられます。以下の例は、上記の親Widgetと子Widgetを切り離すために変更した、親Widgetの実装です。ソフトオブジェクトリファレンス、ソフトクラスリファレンスを使用して強い参照を持たないようにしています。
このようにすることで、リファレンスビュワー上も以下のような表示となり、子Widgetとはソフトリファレンス(弱い参照)となっていることがわかります。
このようにすることで親Widgetをロードした子Widgetのロード+子Widgetのリソースのロードが発生しなくなるので、ロード時間が短くなりスパイクを回避しやすくなります(その代わり子Widgetを使用する際には初回のロードがあります)。これで①や②の問題には対処することができます。
またこのように子Widgetを分離することによって親Widgetとのリファレンスが無くなりGCを適用することができるようになります。上記のノード中の最後にCollect Garbageを実行していますが、この実行によって即時で子Widgetとそのリソースが削除されて③の問題も対処できるようになります。
リファレンスはUIを担当する人もこの部分を理解しておくと、Widgetを実装する際に手戻りが少なくなったり、リソースを適切にコントロールできるようになるので是非おぼえておきましょう。
#4. その他
上記を確認する上で役に立つコンソールコマンドは以下の通りです。
・現在ロード/生成済みのWidget一覧の表示
obj list Class=WidgetBlueprint
・現在ロード/生成済みのTexture一覧の表示
obj list class=Texture
・現在生成されたWidgetの参照元表示
obj refs Name=[WidgetName]
例:obj refs Name=WB_Main