いまさら書けないAssetBundleのcache周りの知見
概要
UnityのAssetBundleをcacheする機構について、以前悩んでたことがあったんだけど、
最近人に聞かれて「ああ~~そういえば」ってなったので書いておく。
Uni本2の内容でコラム欄とかに書いた気でいたんだけど、時間無くて脱落させたままだった。
Unityのひとに聞いて裏付けがてら列挙する。
AssetStoreへのアップ用に使っているUnity4.3.7p4の挙動がベースなので、まあなんだ、古いこと書いてるかもしれない。
新versionのAssetBundleを入れても旧versionのAssetBundleは消えない
WWW.LoadFromCacheOrDownload(url, version, crc)
このメソッドの話で、
新しいAssetBundleを作成、versionに新しい値を希望いっぱいで入れたとする。
古いAssetBundleはどうなるか?
消えん。直感に反する感じする。
たとえばあるAssetBundleを version:1 として読み込んでおいたあと、
同じidentityのAssetBundleを version:2 として読み込むと、
version 1、2ともにキャッシュに残る。やったぜ!
?????ってならない? 俺はなった。
実際のアプリケーションのサイズを観測したらまあ確かに。増えていた。
ただドキュメントのどこを見ても、「cacheからAssetBundleが消える条件」ってのに
「versionが違うのを取得したら古いのは消える」って書いてないので、はい。
厳密に言うと、versionが上がったら、ではなく、cacheされているのと違ったら、なので、
たとえばまあ、1, 100, 200 みたいな書き方でいろんな、同じ名前のAssetBundleをcacheさせる事も可能な訳だ。
、、、、
、、
、
俺はやらないけど。そんな、、なんか、、アレなこと、、
「だったらversionって名前じゃなくてindexとかidとかにすれば良かったのでは?」と思わなくもない。
とりあえず現状でのcacheの実装はそういうものなので、まあなんだ、アレだ、アレ。
version値は固定でいいんじゃないかな。
削除について
AssetBudnleがcacheから消えて行くときは以下のようなルール。
・許可されてる最大cache量(Caching.maximumAvailableDiskSpace)をリミットとして、それを超えそうになると消えていく
・最終参照日時(タイムスタンプ)が古いものから先に消えて行く
・version 1, 2 で1のほうが参照されたタイムスタンプが新しい場合、2のほうが消される順が速い
このへんを正当っぽい感じで利用して、
Caching.maximumAvailableDiskSpace を指定することで、ある程度自動的にcacheのサイジングをするのもアリだと思う。
具体的には、以下を行う。
・新規取得するAssetBundleと、残したいAssetBundleを把握できるようにする
・残したいAssetBundleに参照マークを付ける
・新規取得するAssetBundleの展開後サイズと、残したいAssetBundleの解凍後サイズを合算した値をCaching.maximumAvailableDiskSpaceにセット(ムズイ)
これで、
新規をDLすれば、最近参照マークがついていない(= 最終参照が古い)ものからゴウンゴウン消えて行く。
誤ったcrcを指定して古い( = 使わなくなったので消したい)AssetBundleを消す、っていう方法もあるけど、
想像する動作構造的に大変なんだかなぁ感があるのでこう、はい。
ここで想像してる動作としては、
・versionが合っててcrcが合ってない ->
urlへと同一のidentityを持つAssetBudleを取得しにいく -> (この時点で保持していたAssetBundleを消しているのでは?と思ってる。)
同じidentityのAssetBundleがあったので取得してcache
or
同じidentityのAssetBundleがなんか取得できなかったけどまあいいかでFinish エラーが出る
みたいなの。取得結果を待たずcache消すのファンキーだなあ、ハハハまさかなハハハ。
公式の中の人にこの消し方の話をすると、100%「いや、、あれは、、現状の結果として消えてるだけなんで、、頼るのは、、危険、、、」みたいなお話をしてくれるので、
なんだ、アレだ、この削除方法に頼らない方が良いと思う。
対して、Maxサイズ指定の心太式を用意しておくと、まあ自動的になるのでラクかなという感じはする。
少なくともユーザーを殺さなくてすみそうだ。
あるとうれしい機構としては、重量制限はセットすべき物、として制約をかけることができ、
制約に触れると「アラート出してStreamが流れてくる”親分、この古いやつらどうしやす!?”みたいな問いかけをしてくるメソッドが呼ばれる」
というのがUnityに積まれるのを期待している。
フォルダ単位の監視だったらわりかし簡単に作れると思うけど、その手のフォルダを実機内でハンドルする機構を自分で作るの厭なんだよね。
同一のAssetBundleの更新はラク
さっき紹介したのは削除テクニックではなく、根本的には更新のためのテクニックなので、
その正しい使い方もメモしておく。
同じversionでcrcが異なるものを指定した場合で対象のAssetBundleのcacheを更新できる ので、
更新であればversionを上げずに(versionというパラメータ名がメッチャ直感に反するが)奇麗にキャッシュから古いのを削除、更新されたものを取得、上書き、っていうのができる。
仕様的に、AssetBundleの一意性はAssetBundleの名前(ファイルに埋め込み)で行われていて、
URLがどうなっていようと手に入ったAssetBundle自体のデータで一意性を決めているので、
WWW.LoadFromCacheOrDownload(url1, version, crc1)
で、結局はDL後に取得したAssetBundleのIdentityから「同じAssetBundleかどうか」の判断が行われている。
(version部分で多層的になることはあり得るが。)
で、ただ単に同じAssetBundleを更新したい場合、
WWW.LoadFromCacheOrDownload(url2, 1, crc2)
って書き方で、version値は固定の1、だがcrcが違うはずなので、cacheされているAssetBundleの破棄、取得、cache更新が発生する。
versionって名前付けた奴出てこい、みたいな気分になるが。
更新するのであればこれだけでよくて、なんというかまあ、
使わなくなったAssetBundleの削除に関する事象が大変だなぁという懸念のみが残る。
AssetBundleがいつ展開状態になるか
AssetBundleがDLされ、cacheされ、いつ「Bundle化が解かれるか」みたいな話。
このへんは、App内でAssetBundleが いつから、どんな状態で保持されているか、というのと関わってくる。
BuildAssetBundleOptions.UncompressedAssetBundle を指定しなかった場合、
AssetBundleは、
DLが終わるまでは圧縮された状態で扱われ、
実端末でのAppの内部では、「AssetBundleファイルの状態から、別形式に展開された状態」で保持されている。
AssetBundleのサイズと、実際の端末内での容量の減り方から確認した。
AssetBundle化の効果であるファイルのがっちゃんこ + 圧縮処理によって、
「AssetBundleの元になっているAsset郡のサイズ合計 > AssetBundleのサイズ」
みたいな関係になっているのだが、
App内にDLしたあとに端末の空き容量が予想以上にドカッとが減るので、「ああ、展開して保持してるんだな」という感じ。
DLサイズ = AssetBundleのサイズ なんだけど、保持サイズ = 展開後のサイズ という。
つまり、AssetBundleのサイズとDL後のサイズには関係がない。
この辺、展開後のAssetBundleのサイズを取得するAPIってあったっけな、、無かった気がするな、、(手元でいっぺん開けてサイズを見ればいいのでは、という投げやりな感じ)
なんでこんなの気にするの?っていうと、
この展開後のサイズこそ、Appの重量に直結するし、Caching.maximumAvailableDiskSpaceが計算に使っているパラメータだから。
まあそんなこんなで、BuildAssetBundleOptions.UncompressedAssetBundle を指定しない場合、AssetBundleになった際のサイズを測っても意味は無い。
さらに言うと、AssetBundleは作り出すたびにidentity以外のパラメータが変動するので(作成日時とか入ってるんじゃねーかな)、
もちろんサイズも変動しちゃうので、正確なサイズを出すにはAPIの助けか、展開後のサイズをメモっておく必要がある。
AssetBundleの実機内でのサイズについて
ちなみに、Unity Editor上で特定のプラットフォーム向けにAssetBundleを作り、cacheした状態のファイルは、Macだと以下の場所にstoreされる。
/Users/USER_NAME/Library/Caches/Unity/
で、そこに出てくるファイルはすでにBuildAssetBundleOptions.UncompressedAssetBundle指定無しで作成したAssetBundleだった時のサイズとは異なっている。
圧縮状態からの展開済みって感じのサイズ。ただしファイル数はまだ1つの状態。
PROJECT_NAME/フォルダ/AssetBundle(圧縮解除後) という並びになっている。
AssetBundleのidentityごとにフォルダが作られる、って訳ではなく、
AssetBundleのidentity x version の組み合わせで、同じAssetBundleなんだけどversion分だけファンキーな感じでフォルダが作られる。
versionの意味を勘違いして1,2,,みたいに上書きするつもりでAssetBundleをキャッシュして読み込むと、ここのフォルダが無限に増えていく。
で、内容のファイル名もidみたいな感じなんだけど(上記でいうCAB-863e4~ っていう名前のファイル)、
Finder上と、実際のcache後に取得できる解凍済みAssetBundleのサイズに差がある。
Caching.spaceOccupiedの値をみる限り、端末内のcacheを食う量はわずかに増える傾向にある。というかドンピシャで53byte増える。
プラットフォームやバージョン、比較対象ごとに違う可能性があるが、
Unity4.3だと、Finderで確認できるAssetBundleのサイズと、cacheを走らせたタイミングでのCaching.spaceOccupiedの値を比べて、+53byteのサイズ差がでた。
cache時にCaching.spaceOccupiedで吐かれる値が + 53byteでかい。
この53byte、Mac上だと、ちょうど同じフォルダにある__infoってファイルのサイズと一致するんで、
Caching.spaceOccupiedは、__info を含めた、このフォルダ全体の重さを計ってる感じ。
ちなみにiOS上で計ってみたところ、機種差があるかどうかはわからないが、+53byteで同じ結果がでた。
__infoファイルが作られてるんだろうなあきっとという感じ。
ところでAssetBundleは作り直すたびに違うサイズになる
上記でも触れたけどそういう感じ。
内容は一緒でも、作り直すたびにサイズが変化(誤差レベルだけど確実な変化)、もちろんcrcも変化する。
ただしcache後のサイズは変わらない。構成要素が一緒なんだからまあそう。
感想
デフォルトで存在するAPIとしては、AssetBundleは魅力的なんだけど、
ゲームの要件にそぐわない感じでなければ使いたいなあ。
デフォルトで入ってるので。
こういうところであんまり頑張りたくない。
こちらからは以上です。