ss5playerForUnity1.4.5のGC Allocチューニング

# はじめに
　SpriteStudioのUnityPlayerが1.4.5バージョンになって、GC Allocが少なくなるような修正が入った。

　ただし、GC Allocが少なくなるのにはいくつか条件があって、最悪パターンを引くと以前と同じようなゴミが発生する。てなわけで、この記事ではどのようにしたらGC Allocが少なくなるかを説明する。

## 参照　該当コミット
https://github.com/SpriteStudio/SS5PlayerForUnity/commit/c0ba82921c60af1490df8b9fd4f11a641444fa10

## 注意

### playerのバージョン

　1.4.5以前のバージョンはこの記事の内容は関係なし。

　また、この記事で言及するやりかたはあくまでも1.4.5バージョンに対応するだけのものすごーーーーーくピンポイントなやり方であって、普遍的に通用するような手法ではない。以降のバージョンを使ってる場合は、効果があるかはちゃんと確認しよう。

### GCだけがネックじゃない

　この記事はあくまでもGC Allocを減らすにはどのようにすれば良いかに言及するだけで、その先にある最終的なパフォーマンスチューニングはまた別の話。メモリやCPUがネックになってるならそっちの負荷を減らすことから始めるのが普通だ。

## 確認方法

　プロファイラを使う。

![スケッチ6.png](https://qiita-image-store.s3.amazonaws.com/0/36784/e0ea14de-131e-aa47-48d0-b075a8b88850.png)

# 本題　GC Allocが減る条件

　GC Allocが減る条件は2つある。これらは片方だけ満たしても効果がある。

## 条件1. メッシュの数が前フレームから変わらないとき

　メッシュ数はだいたい表示パーツの数と同じと考えていい。たとえばキャラが単に腕や足を振ってるだけなんかの時はGC Allocは発生しないのに対し、表示パーツがアニメーションによって増えたり減ったりするとGC Allocが発生する。

　また、ManagerDraw下にあるSpriteStudioオブジェクトが減ったり増えたりするときもGC Allocが発生する。

### コード
https://github.com/SpriteStudio/SS5PlayerForUnity/blob/c0ba82921c60af1490df8b9fd4f11a641444fa10/Assets/SpriteStudio/ScriptLibrary/Library_SpriteStudio.cs#L5994-L5997

　`CombineInstance[]`を使いまわすことでGC Allocを減らしている。`Length`が変化すると使いまわせなくなるので`new`する必要があり、GC Allocが発生する。

## 条件2. Materialが1つしかないとき

　SpriteStudioの扱うMaterialはテクスチャごとに複数存在し、それぞれミックスとか加算とかのブレンドモードを表現している。Materialが1つしかないというのは、ManagerDrawが表示しているパーツがすべて同じテクスチャ、同じブレンドモードの状態を指す。

　ダメなパターンだと、例えば、ManagerDrawの下にテクスチャの違う2種類のキャラを表示するとダメ。1種類のキャラしかいなくても、パーツの中にミックスパーツと加算パーツが混ざってるとかでもダメ。

　ちなみに、インスペクタのMeshRendererを確認すれば、使っているMaterialを確認できる。これが1つならok。例えば↓の画像だと2つ使われている。

![スケッチ5.png](https://qiita-image-store.s3.amazonaws.com/0/36784/c1a23ccb-2baa-e9f4-d073-4a5e4d596889.png)

### コード
https://github.com/SpriteStudio/SS5PlayerForUnity/blob/c0ba82921c60af1490df8b9fd4f11a641444fa10/Assets/SpriteStudio/ScriptLibrary/Library_SpriteStudio.cs#L6036-L6053

　`VertexNoTriangle`の`new`は勿論のこと、`InstanceMesh.triangles`でもGC Allocが発生することに注意。
　（ここの処理、`Mesh.CombineMeshes`の引数に結合後のsubmeshIndexを指定できれば解決するんだけどなあ。）

## 総合すると

　上2つの条件を満たすためには、1つのManagerDrawで多くのSSオブジェクトを表示するのではなく、1つのManagerDrawに1つだけSSオブジェクトを割り当てるのが良い感じになりやすいことになる。

　（当然、1キャラ内に複数のブレンドやテクスチャがあったり、スゴイ勢いでパーツ増減アニメーションが組まれてたらどうしようもない。その時はsspj作成の際のルールを見直すか、もしくはplayerを独自カスタマイズするか。）

# 追記：バージョン1.5.3

　ss5PlayerForUnityの最新バージョンが1.5.3になってた。
　GCに関しては1.4.5からさらにマシになってるので、もはやそこまでネックにはならないんじゃないかなーという感じ。`Material`が複数ついててもまあまあ大丈夫。
　これがどのくらいかというと、上記にある1ManagerDraw-1SSオブジェクトで構成するとGC云々よりもCPU負荷のほうが高くなる場合がある。素直に`ManagerDraw`をひとつだけの構成にしたほうが良いかも。実際に比較して判断しなきゃいけない。

## どうしてものときのカスタム
　それでもやっぱりGCがきついとき。
　作業領域を弱参照でもってる箇所があるんだけど、とうぜん弱参照なのでそのうち解放されて再取得するフレームが発生する。で、このときGC Allocが発生する。この頻度はメモリの状況によりけりなので、ひょっとするとすごい勢いで再取得が行われる場合があるかもしれない。
　そんなときは。以下の`WeakReference`を`List<int>`に換えてやる。普通の`List`なので勝手に開放されなくなるわけです。

https://github.com/SpriteStudio/SS5PlayerForUnity/blob/496b2ce35e997a9880cab1fc890083d21ea24251/Assets/SpriteStudio/ScriptLibrary/Library_SpriteStudio.cs#L5012-L5018

　`static`に`List`を置くことになるので、行儀は良くない。シーン切り替え時とかに手動で開放してやること。

## CameraTargetの罠

　前からなんだけど、`ManagerDraw`に`CameraTarget`っていうフィールドがあって、ソート時にどのカメラを使うかっていうものなんだけど、カメラ探しに失敗してnullのままになるとすごい勢いでGC Allocが発生する。