AssetFileHashが一貫性を失うタイミングについて
概要
Addressablesがうまいこと完成しても、中で使っているのはAssetBundle。はい。
AddressablesはABの作成からダウンロード用のリストの生成、それを起動時に取得しての、オンデマンドや範囲による一括取得を可能にしている(動けば)
ただ、中身はまんまABを使うオープンソースのコントローラ、というかフレームワークで、
まあ、フレームワークってことはユースケースしっかり判断して使おう・使わないでおこう を決めようなというところ。
そしてABには、特にABの更新に関わる crc 、hash という2つのパラメータがある。
crcって何
cyclic redundancy check、要するに、データが変更されたかどうかをチェックするための値。
エラー訂正(データの内容が期待してたんと違う)みたいなのを検知することができる。
ABで使われているcrcは、その生成にABの内容、中身のファイルの生成時の時刻などを使用しているらしく、
中身のAssetが変化するたびに変わる。(そりゃそう)
この変化によって、例えば次のようなことをやっている。
AB1を生成
-> AB1のcrcはこれ
-> どっかにメモっておく
-> クライアントアプリケーション側でメモを取得
-> AB1をダウンロード
-> メモのcrcと比較して、欲しかった「作った時のAB1のcrc」と、取得してきたAB1のcrcがマッチするかを見て、ちゃんとダウンロードできたか確認。
実は、このフローのうち、[AB1をダウンロード]以降は、UnityWebRequestのABダウンロード用の関数の引数がそのままその用途をなしている。
APIとしてはこのへん。
public static Networking.UnityWebRequest GetAssetBundle(string uri, uint crc);
https://docs.unity3d.com/ScriptReference/Networking.UnityWebRequestAssetBundle.GetAssetBundle.html
ここのcrcが、まさに「これからダウンロードするABのcrcに対して、マッチするのを期待するcrc」という値になっている。
ちなみにcrc入れて期待したのと違うのが帰ってくると、エラーが出て(表示だけのエラー、キャッチできない、今回落としたABは保存されない)、ダウンロードしたABはnullになる。
webからのダウンロードはファイルがぶっ壊れることがままあって、crcによる「落としたファイルが正しいか」みたいな処理は必須になっていると思う。
で、このcrcは、AB作成時にmanifestファイルに書いてある。
nestedprefab.manifest
ManifestFileVersion: 0
CRC: 2200902407
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: c088a4b2797e99ed3c95d8ce957816af
TypeTreeHash:
serializedVersion: 2
Hash: 11817dad4d3c8e9c24248d58700284ea
HashAppended: 0
ClassTypes:
- Class: 1
Script: {instanceID: 0}
- Class: 4
Script: {instanceID: 0}
- Class: 23
Script: {instanceID: 0}
- Class: 102
Script: {instanceID: 0}
- Class: 114
Script: {fileID: 11500000, guid: 7e73406419bf54a50a1b5faf24749c6c, type: 3}
- Class: 115
Script: {instanceID: 0}
Assets:
- Assets/AutoyaTests/RuntimeData/AssetBundles/MainResources/nestedPrefab.prefab
Dependencies:
- /Users/passepied/Desktop/autoya/Editor/AssetGraph-1.3-release/UnityEngine.AssetBundleGraph/Cache/AssetBundles/4e024a18-af2d-4f29-9f4b-1212e005d1ea/iOS/texturename1
赤字のところがcrc。
UnityはABを生成するとき、1つのABに対して1つのcrcを計算し、それを記述したmanifestファイルを吐く。
で。crcには、Unityのプロジェクトの形態を無茶苦茶にしても一貫した値を出す、という結構強い一貫性がある。
具体的には、昨今みんながやってるであろう「Libraryフォルダをバージョン管理しない」みたいなことをしても、ちゃんと一貫したcrcを出す。
crcの一貫性とプロジェクト形態の例
・これはLibraryフォルダが無い状態からでも同じcrcを出す
・たぶんABにする対象のアセットのmetaファイルの値さえしっかりしてればOK
・metaファイルすら失うと死(metaが作り直されてABのcrcも当然変わる
Libraryフォルダをgithubとかに上げないでもいい、みたいなのはみんなやってると思ってる。だから、
Libがなくてもcrcが変わらない、という事態は大変好ましい。
問題はhashだ。
hashって何
hashもUnityのAB取得のAPIに記載がある。
public static Networking.UnityWebRequest GetAssetBundle(string uri, Hash128 hash, uint crc);
https://docs.unity3d.com/ScriptReference/Networking.UnityWebRequestAssetBundle.GetAssetBundle.html
それと、以前書いたhashに関する記事で、hashって何?どう使うの?ってのを紹介している。
Caching.IsVersionCached(url, hash) ってどういう挙動?
http://sassembla.github.io/Public/2017:02:10%2013-56-21/2017:02:10%2013-56-21.html
んでこのhash、どうやってつけるのがいいんだろう、って悩むんだけど、なんとABを生成した時に出るmanifestファイルに2種類のhashが書いてある。
ManifestFileVersion: 0
CRC: 2200902407
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: c088a4b2797e99ed3c95d8ce957816af
TypeTreeHash:
serializedVersion: 2
Hash: 11817dad4d3c8e9c24248d58700284ea
HashAppended: 0
ClassTypes:
- Class: 1
Script: {instanceID: 0}
- Class: 4
Script: {instanceID: 0}
- Class: 23
Script: {instanceID: 0}
- Class: 102
Script: {instanceID: 0}
- Class: 114
Script: {fileID: 11500000, guid: 7e73406419bf54a50a1b5faf24749c6c, type: 3}
- Class: 115
Script: {instanceID: 0}
Assets:
- Assets/AutoyaTests/RuntimeData/AssetBundles/MainResources/nestedPrefab.prefab
Dependencies:
- /Users/passepied/Desktop/autoya/Editor/AssetGraph-1.3-release/UnityEngine.AssetBundleGraph/Cache/AssetBundles/4e024a18-af2d-4f29-9f4b-1212e005d1ea/iOS/texturename1
自分はこのうちでAssetFileHashを使うことが多い。
まあぶっちゃけ、ここでのhash自体は
・ABの要素が変わった時に自動的に変わってくれるとうれしい
・ABの要素が変わらないなら変わらないでいてほしい
という感じで、このAssetFileHashは「今まで自分が知る限り」その用途を果たしてくれていた。
が。
がだ。
その前提が砕けるタイミングがあった。(本題)
AssetFileHashが全くランダムに作られてしまうタイミングがある
ぶっちゃけて、Libraryフォルダを生成するタイミングで、AssetFileHashの値が無駄に変化する。
この、Libraryフォルダを作り直すとAssetFileHashの一貫性が破綻する状況は、次のようなシナリオで露見した。
1.あるプロジェクトでABを生成することになった
2.まずはPC1で適当にABを生成していた
3.ABのhashパラメータとしてはAssetFileHashを使用していた
4.hashはAという値を出していた
5.githubとかでプロジェクトをバージョン管理、Libraryフォルダは含めない(要らないからね!!)
6.別のPC、PC2にプロジェクトをpullしてきて展開、このタイミングではLibraryフォルダはない。
7.Unityを起動すると、Libraryフォルダが生成される。
8.ここでPC2でABを作成すると、アセットは変化していないのでcrcやサイズなどは一切変化しないが、AssetFileHashは 3. の時とは別の値を生成する。なんでやねん。
なんとなく嫌な予感がして調べてもらったところ完全に上記の状況がヒットして、ぐえーーってなっていた。
AssetFileHashはLibraryフォルダが共有されないと一貫性を失ってしまう
つまり
・それまで変化がなければ一定の値を保っていた
・変化があった時「のみ」変わっていた
という特性だと思っていたのが、Libraryフォルダを生成するようなことがあるとこの一貫性が破綻する。
一応、Libフォルダが生成されることがトリガーで値が変わる、というのがわかっている状態だが、
これは例えば、
「先祖代々ABを作成していたマシンが死んだ」
「新たにABを生成するマシンにプロジェクトをpullして~」
みたいなタイミングで牙を剥く。
具体的にいうと、
アプリ側でABDLに際してhashチェックするようなコード書いてると
すべてのABが更新されたような挙動になってしまう。実際にはなにも変わっていないのに。
んんん~~~AssetFileHash なんでやねん。
というわけで、ABのhashには、AssetFileHashとは違った値を使おう。
オススメは次の特性を満たす値。
・crcのように、ファイルの内容が変わったら10000%変わる値
・上記以外の要素を一切含まない値
自分はどうするかな~~ ABの名前とcrcからhash作るかな。
そのうちこの辺に反映される。
Addressablesが将来、このhash要素の概念を守ってくれていると嬉しい。(また最新見てみよう。)
あ、バグ、、とかそういう線はあるのかな、、うーん、、、
こちらからは以上です。