[UE4]GC Clusterによって開放されない問題について

検証UE4: 4.27.2
まちがってたらごめんなさい。

# まとめ
- 別のClusterに同一アセットが入っていると開放できない。
    - 参照するアセットに注意。
    - 開放する順番にも注意。
- Cluster Rootの対象を増やす際は注意。
    - エンジンデフォルトだとParticleSystem/Material等だけで問題が出にくい
    - 自前で増やす際は注意
  

# 参考
- [UE4]GCClusterとその動作を調査する方法 - Qiita EGJ-Takashi_Suzuki(Epic Games Japan)
    - https://qiita.com/EGJ-Takashi_Suzuki/items/2c96c4e6f37cc5028207
- 『FINAL FANTASY VII REMAKE』におけるプロファイリングと最適化事例 【UNREAL FEST EXTREME 2021 SUMMER】 - docswell
    - https://www.docswell.com/s/EpicGamesJapan/ZEEQEZ-UE4_UFE21S_SQEX_FF7R_Profiling#p44


# Cluster Rootの対象
デフォルトでCluster Rootになり得るのは、下記のみかと思います。(C++側のCanBeClusterRootを確認）
・Material
・ParticleSystem（Cascadeの方）

プロジェクト設定で下記もRoot化できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/649ac6f4-5eae-77ab-c17f-83a089c51b5a.png)
・BP
　→ 「ブループリントクラスタリングが有効」(gc.BlueprintClusteringEnabled)

またC++では CanBeClusterRoot を override してRoot化するか指定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/79d85c17-a2d8-3083-7bf2-0ba2079b7781.png)

# 開放されないCluster
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/c58f7a9d-fc77-0826-c06c-dcdf16f390cc.png)

## 検証
こんな構成の、パーティクルシステムが配置されたサブレベルが有ります。
Level[A] - ParticleSystem[A] - Material[A] - Texture[Green]
Level[B] - ParticleSystem[B] - Material[B] - Texture[Green]

このレベルをLevelStreamingで下記の順でロード操作します。
１）LevelA -> ロード
２）LevelB -> ロード
３）LevelA -> アンロード
４）LevelB -> アンロード

- ロード前
    - ```
      Obj List: Class=ParticleSystem
      0 Objects
      ```
- １）LevelA -> ロード
    - ```
      Obj List: Class=ParticleSystem
      ParticleSystem /Game/PS_A.PS_A
      ```
- ２）LevelB -> ロード
    - ```
      Obj List: Class=ParticleSystem
      ParticleSystem /Game/PS_A.PS_A
      ParticleSystem /Game/PS_B.PS_B
      ```
- ３）LevelA -> アンロード
    - ```
      Obj List: Class=ParticleSystem
      ParticleSystem /Game/PS_A.PS_A　←※
      ParticleSystem /Game/PS_B.PS_B
      ```
- ４）LevelB -> アンロード
    - ```
      Obj List: Class=ParticleSystem
      0 Objects
      ```


**（３）の「LevelA アンロード」の時点で"PS_A"は使用がなくなった筈ですが、開放されません。**
（４）の「LevelB アンロード」されると開放されます。

## 問題
開放されないという事で、いつも通り誰が持ってるか調べようと参照元を辿る **obj refs** で見ると下記のように出てきます
```
obj refs Name=PS_A
LogReferenceChain: (standalone) (ClusterRoot) ParticleSystem /Game/PS_A.PS_A is not currently reachable.
```
調べようとしても誰が持ってるか分からない状態になっています。

この場合は、 **gc.DumpRefsToCluster Root=\<Root名\>** でCluster参照を出力できます。
```
gc.DumpRefsToCluster Root=PS_A
LogObj: Display: Dumping references to objects in cluster ParticleSystem /Game/PS_A.PS_A
LogReferenceChain: (root) (NeverGCed) GCObjectReferencer /Engine/Transient.GCObjectReferencer_2147482645::AddReferencedObjects(): FParticleSystemWorldManager
LogReferenceChain:  ParticleSystemComponent /Game/LV_SubB.LV_SubB:PersistentLevel.PS_B_2.ParticleSystemComponent0->Template
LogReferenceChain:   (standalone) (ClusterRoot) ParticleSystem /Game/PS_B.PS_B->Emitters
LogReferenceChain:    (Clustered) ParticleSpriteEmitter /Game/PS_B.PS_B:ParticleSpriteEmitter_0->LODLevels
LogReferenceChain:     (Clustered) ParticleLODLevel /Game/PS_B.PS_B:ParticleSpriteEmitter_0.ParticleLODLevel_0->RequiredModule
LogReferenceChain:      (Clustered) ParticleModuleRequired /Game/PS_B.PS_B:ParticleModuleRequired_0->Material
LogReferenceChain:       (standalone) (Clustered) Material /Game/M_ParticleB.M_ParticleB->TextureValues
LogReferenceChain:        (standalone) (Clustered) Texture2D /Engine/EngineResources/AICON-Green.AICON-Green->Outer
LogReferenceChain:         (Clustered) Package /Engine/EngineResources/AICON-Green
```


原因としては、 <font color="OrangeRed">**別々のClusterで同じアセットを参照している事が問題になります。**</font>

パーティクルシステムはClusterRootになるObjectです。
PS_A, PS_BはそれぞれClusterのRootになっています。

gc.ListClusters
```
LogObj: Display: ParticleSystem /Game/PS_A.PS_A (Index: 15945), Size: 15, ReferencedClusters: 0
LogObj: Display: ParticleSystem /Game/PS_B.PS_B (Index: 15970), Size: 13, ReferencedClusters: 1
LogObj: Display: Displayed 2 clusters
LogObj: Display: Total number of clusters: 2
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/9c5e8596-2ecd-289d-abd6-4293a62050da.png)
PS_A, PS_Bの２つで使用しているテクスチャが同じアセットを使用しています。（上記赤枠）
ロード順で、該当テクスチャは先にロードされたPS_Aのクラスタに入っています。
その為、PS_Aを開放するにはテクスチャの開放が必要。
→ 参照先(PS_B)が開放されていないと、PS_Aは開放できません。


# 対策
- Cluster Rootの対象を考慮する
    - エンジンのデフォルト的に今回はParticleSystemですが、
      前述の設定であるようにBPやC++で独自に対象にすると問題が色々出る事が考えられます。
- 強制的に開放する
  該当の開放できない状態（（３）のLevelAアンロードした状態）で、
  ```
  gc.CreateGCClusters 0
  ```
  を実行してClusterを開放。次にGCが走ると、開放できなかったPS_Aが開放されます。
  必要があれば再度 gc.CreateGCClusters 1 に戻すとか。

## 開放する順番について
アンロード順を変更して、Bを先に開放すると、問題なく開放されます。
PS_Bで使用する該当のテクスチャアセットが、PS_Aクラスタに入っている為
１）LevelA -> ロード
２）LevelB -> ロード
３）**LevelB** -> アンロード
４）**LevelA** -> アンロード

- ロード前
    - ```
      Obj List: Class=ParticleSystem
      0 Objects
      ```
- １）LevelA -> ロード
    - ```
      Obj List: Class=ParticleSystem
      ParticleSystem /Game/PS_A.PS_A
      ```
- ２）LevelB -> ロード
    - ```
      Obj List: Class=ParticleSystem
      ParticleSystem /Game/PS_A.PS_A
      ParticleSystem /Game/PS_B.PS_B
      ```
- ３）LevelB -> アンロード
    - ```
      Obj List: Class=ParticleSystem
      ParticleSystem /Game/PS_B.PS_A
      ```
- ４）LevelA -> アンロード
    - ```
      Obj List: Class=ParticleSystem
      0 Objects
      ```