もんりぃ is undefined.

育児ネタとか、技術ネタとか。

AssetBundle の地雷を踏んだお話し

はじめに

今日また Unity の AssetBundle の地雷を踏んだので、同じ地雷を踏まないように記録として残します。

なお、この地雷は私自身が埋めたものであり、普通に使う分には起きないものなのでご安心ください。

経緯

とあるプロダクトの AssetBundle を更新したところ、以下のような不可思議な現象が発生するようになった。

  • 最新版(v1.1.0 としておく)を新規インストールする分には問題ない
  • 一つ前のバージョン(v1.0.0としておく)を(DeployGate などから)インストールする分にも問題ない
  • v1.0.0 で AssetBundle をダウンロードしておいて、v1.1.0 で AssetBundle を更新すると 一部の AssetBundle のみ更新されない
  • しかも、「v1.0.0 でダウンロードだけした場合」は問題無く、「v1.0.0 でダウンロードし、実際に使われている画面まで進んだ場合」が NG

とても不可解な現象で、CDN のキャッシュとかも疑ったけど問題無さそうだった。

前提

とあるプロダクトの AssetBundle 要件としては以下のような感じだった。

  • タイトル画面に入るタイミングで必要な AssetBundle を全てダウンロードする仕組みになっている
  • AssetBundle Manifest を用いずに、URL のみでキャッシュ管理を行っている
  • URL の中に各 AssetBundle のバージョン的なものを示す数値が含まれる
    • https://example.com/v1/foo.unity3d
    • https://example.com/v1/bar.unity3d
    • https://example.com/v2/foo.unity3d
    • みたいな感じ
  • ダウンロードのみを行うメソッドと、読み込みを行うメソッドが分かれている

原因

前提のなかで記載している「読み込みを行うメソッド」の中で使っている UnityWebRequestAssetBundle.GetAssetBundle()オーバーロード(string url, uint crc) ではなく (string url, Hash128 hash, uint crc) になっており、更に hash には new Hash128() を渡すという状態になっていた。

分析

詳細な仕様は分からないが、キャッシュ周りの仕組みは以下のようになっていると考えられる。

なお、 crcチェックサム)については話がヤヤコシクなるので割愛。

  1. ダウンロードされた AssetBundle を GetAssetBundle() の引数に応じて保存
    • hashversion が渡されていない場合は、AssetBundle の実体から取得できる AssetBundle Name *2 をそのままファイル名として利用して保存 *3
    • hashversion が渡されている場合は、 AssetBundle Name と hashversion をファイル名に加えて利用して保存
  2. GetAssetBundle() の引数に渡された URL と AssetBundle Name との対応表的なものを別途保存
    • ココでは hash とか version は記録されていないんじゃないかと思われる

で、今回のケースの場合、ダウンロード時と読み込み時に引数が異なってしまっていたコトが原因で、以下のような感じになったものと推察される。

ここでは、 https://example.com/v1/foo.unity3d, https://example.com/v2/foo.unity3d という AssetBundle をダウンロードする場合を考える。

  1. v1.0.0 のダウンロード処理
    • 対応表に https://example.com/v1/foo.unity3d に該当する AssetBundle Name が見付からないのでダウンロードが開始される
    • hash ナシのファイル foo.unity3d が保存される
    • 対応表に "https://example.com/v1/foo.unity3d":"foo.unity3d" 的な情報が書き込まれる
  2. v1.0.0 の読み込み処理
    • 対応表に 1. で保存されたhttps://example.com/v1/foo.unity3d に該当する AssetBundle Name はあるが、キャッシュの実体として hash アリのファイルが見付からないので、ダウンロードが開始される
    • hash アリのファイル 00000000000000000000000000000000-foo.unity3d が保存される
      • new Hash128() を渡してしまっているので、実際のハッシュ値00000000000000000000000000000000 とかになると思われる
  3. v1.1.0 のダウンロード処理
    • 対応表に https://example.com/v2/foo.unity3d に該当する AssetBundle Name が見付からないのでダウンロードが開始される
      • URL のバージョン番号のところが異なるのでキーがないはず
    • hash ナシのファイル foo.unity3d が更新される
    • 対応表に "https://example.com/v2/foo.unity3d":"foo.unity3d" 的な情報が書き込まれる
  4. v1.1.0 の読み込み処理
    • 対応表に 3. で保存された https://example.com/v2/foo.unity3d に該当する AssetBundle Name があり、更に 2. で保存された hash アリのファイル 00000000000000000000000000000000-foo.unity3d が存在するため、キャッシュから読み込みが行われる
    • 結果として古いファイルが読まれてしまう!

所感

いや、はい。同じオーバーロード使わなかったオレが悪いんです。

引数省略せずに、素直に AssetBundleManifest.GetAssetBundleHash() とかで取得出来る Hash を使うと良いんじゃないでしょうか。

この辺のキャッシュの詳細な資料ってどこかに転がっていませんかねぇ…?

*1:ホントは改竄対策とか中途切断のコトとか考えるとダメなんだけど、日和ってます

*2:Inspector で設定するアレ

*3:いや、内部的にはハッシュ値とか取ってるかもだけど分からん