AssetBundleの非同期ロードを中断する方法

今回の主な話題：AssetBundleの非同期ロードを中断する方法、コードがdllにパッケージされた後にProfilerできません、updateWhenOffscreenの割役

---
###ロード
Q1: 非同期インターフェースyield return AssetBundle.ASyncLoadを使用する場合、そういう疑問が起きました：非同期処理が完了する前にどうやってこのタスクをキャンセルできますか。つまり、AssetBundleが半分ロードされていますが、ロードを諦めたいのですが、どうすればよいですか？

①
>Unityインターフェースには中断操作はありませんが、プロジェクトのAssetBundleMgrモジュールのビジネスロジックレイヤーに中断できるインターフェースを提供できます。つまり、アセットがMgrにロードされた後、ビジネスレイヤーが不要になった場合、コールバックは返されません/実行されません。この「不要な」アセットをキャッシュまたはアンインストールする必要があるかどうかについては、自分のプロジェクトに従って処理する必要があります。

②
>Unityドキュメントにある小さなトラップを共有しましょう。実際に答えはわかりません。ソースコードを知っている友達が助けてくれることを願っています。
AssetBundleが非同期で要求されると、AssetBundleCreateRequestオブジェクトが返されます。Unityの公式ドキュメントには、AssetBundleCreateRequest.assetBundleを述べる時には、下記の文が記載されています。

>“DescriptionAsset object being loaded (Read Only).　Note that accessing asset before isDone is true will stall the loading process.”

>「stall」という言葉が使っていますが、前に「停止」の意味と理解し、このインターフェイスのコールは非同期要求を中断できると考えていました。この特性を乗って、「同じAssetBundleが非同期にロードされているときに同期要求が来ました」状況で使用しました。
今日この問題を見て、このドキュメントを読むと、「stall」という言葉は「停止」の意味ではなく、「遅延」を意味する可能性が高いと感じています。非同期ロード要求の場合、スレッドで実行しますが、このときメインスレッドからのアクセスリクエストがあるとクロススレッドロックロジックが必要になり、ロードが遅くなる場合があります。 停止する場合は、ドキュメントでStopなどのより具体的な単語を使用する必要があると思います。

③
>問題主が私たちと同じ問題に遭いましたね。最後に、Promiseを使用してアセットのロードインターフェイスをカプセル化しました。
返されたhandleオブジェクトの状態が固化すると、変更できません。

>```ruby:code1
op = loadasset()
yield return op
if op.error != nil then
  log("fail=" + op.error)
end
```

>```ruby:code2
op:reject("next scene is loading")
```

④
>APIとUnityReferenceSourceを見に行きました。プロジェクトはずっとUnity5.6バージョンを使用しているため、Unity2017および2018のAssetBundleはまだテストされていません。Unity 5.6は、AssetBundleアセットをキャンセルまたはブロックすることはできませんが、新しいDocにブロックはアセットを取得できると記載されています。referenceSourceにあるAssetBundleのAPIも言及していますが、AssetのAPIには言及していません。私の理解では、非同期が完了する前に、アセットをアクセスすると同期になってすぐにこのリソースを取得できます。テストしたことのある学生さんがこれを答えてください。本当にそうであれば良かったです。多くのプロジェクトは、デザイン的に非同期と同期は共存して、ただ「一つ非同期ロードしているアセットを同期ロードする」ことを避けましただけ。Unityがこの辺を変更すると、「一つ非同期ロードしているアセットを同期ロードする」という問題を完璧に解決できます。
Unity Docによく騙されたため、ReferenceSourceにもっと信じています。AssetBundleは非同期ロードしているアセットを同期ロードすることを実現したが、Assetには実現していないと推測します。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/b2d1b6b0-f9e7-1328-1bc2-a481daf51dd4.png)

---
###アニメーションシステム
Q2：キャラクターの動きを作るために70本の骨（準備済みリボンが多い）を試用したいとアーティストと話し合いましたが、各モデル（各セットのファッション）はそのうちの30〜40本しかスキニングに参加しません。この状況と40本の骨を使ってキャラクターの動きを作る（Optimize GameObjectを開く）と比べると、パフォーマンスに大きな違いはありますか？

この考えの理由は、ファッションの実装のためにより多くのスペースを確保したいからです。最初は、コードを使ってすべてのリボンを動的なボーンに制御することを計画しましたが、Optimize GameObjectをオンにすると、スキニング骨格情報を変更できなくなります。これに対して何か方法はありませんか？
>一つ目の問題に対して、2つの状況のパフォーマンスの違いを比較するのは難しいです。Optimize GameObjectがオンになっているかどうかに関係なく、最も影響を受けるパフォーマンスコストは、下図にあるDirtySceneObjectsとWriteJobであります。そして骨格の数が影響するのは下図にあるProcessAnimationsJobです。だからこの両者はお互いに大きな関連性はありません。したがって、問題主に適切かどうかを判断する前に、実機で自分のモデルに対してテストをすることをお勧めします。
![2-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/78c20214-3b22-e2d2-e133-77b47a5faf6b.png)
二番目の問題を答えます。一般的に、リボンは追加のアニメーションファイルとして処理できます。Optimize GameObjectを行う時に、リボンがキャラクターにかける親ノードをバレさせたら大丈夫です。そうすればリボンは自分のアニメーション計算を行え、元のキャラクターと結合する必要はありません。もちろん、上記は一つの一般的なやり方で、問題主に最も適切のものはプロジェクト次第で決定する必要があります。

---
###ロジックコード
Q3：開発プロセス中に、デザイナーアーティストが私たちのコードを使用しました。C＃ファイルをdllファイルにパッケージし、パフォーマンスを分析する時にProfilerを使ってドットを追加してロケーションする必要がります。しかし、dllにドットを追加すると、Unity Profilerパネルにデータが表示されません。
>dllをパッケージするプロジェクトにマクロを追加する必要があります：ENABLE_PROFILE
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/66a08698-7ca4-54a9-1c82-95551986dbc2.png)

---
###レンダリング
Q4：updateWhenOffscreenの用途は何ですか？オンにすることをお勧めしますか？私たちの髪はalpha blendingのShaderを使っていますが、時々に理由もなくで消える状況はあり、これは原因かどうかを疑っています。

①
>主にBounds：https://docs.unity3d.com/ScriptReference/SkinnedMeshRenderer-localBounds.html

>オンにすれば、フレームごとにBoundsを更新します。しかし、updateWhenOffscreen自身のドキュメントを見ると、困惑しています。
https://docs.unity3d.com/ScriptReference/SkinnedMeshRenderer-updateWhenOffscreen.html

>実際にテストしたら、チェックすると、画面を離れなくでもフレームごとにBoundsを更新します。チェックしないと、画面にあっでも更新しません。そしてupdateWhenOffscreenを無効化するとanimationsも無効になさせる効果は見つけませんでした。オンにすると追加のコストが必然的に発生するため、当然、オンにしないことをお勧めします。

②
>この問題はBoundsに関連しているようです。RecalculateBoundsを前処理することをお勧めします。

---
###メモリ管理
Q5：パフォーマンスレポートにある「Monoメモリの割り当て詳細」レポートから、一番目のZOutputSteam.ctorに50MBが割り当てられていることがわかりましたが、この部分はどうやって最適化できますか？しかし、「コード効率/Monoメモリの割り当て」からこれは見つけませんでした。
![5-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/15d02df3-32bb-0570-0ead-5c845b22537a.png)
![6-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/686d5965-68f3-a083-2a7c-b01b7132eefd.png)
>ZOutputSteam.ctorとは、ZOutputSteam類が構造関数を実行していることを説明します。つまり、コードが複数回にNew ZOutputSteam操作を執行しています。問題主にこれを確認することをお勧めします。そうであれば、頻繁なNewの代わりに、ZOutputSteamの実例をサンプリングと再利用することを考えられます。

>「コード効率/Monoメモリの割り当て」で表示される情報は、全体的なMonoメモリ割り当て情報だけです。だらか、Monoメモリ検測を実行する必要があります。

---
UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、**パフォーマンス分析**と**最適化ソリューション**及び**コンサルティングサービス**を提供している会社でございます。

UWA公式サイト：https://jp.uwa4d.com
UWA公式ブログ：https://blog.jp.uwa4d.com