【Unity】【Addressable】ストレージ容量不足で書き込みが失敗するエラーをハンドリングする

Unity の Addressable アセットシステムでストレージ容量不足で書き込みが失敗するエラーをハンドリングする方法についてまとめました。

はじめに

ダウンロードコンテンツは、ダウンロード先のストレージに十分な空き容量がない場合には、当然ですがダウンロードできません。
以下のフォーラムでは、Addressable アセットシステムにおいてストレージ容量が少ないことによる書き込みエラーのハンドリングについて議論されています。

forum.unity.com

UnityのCachingの仕組みやUnityWebRequestとAssetBundle圧縮の仕組みなどに話が及んで複雑なので、この記事に要点をざっとまとめておきます。

結論

結論からまとめると、 Unity の仕組み(UnityWebRequest / Caching)を使う限り、ストレージ容量にダウンロードのために十分な空き容量があるかどうかを事前に完璧にチェックすることは不可能です。
ダウンロード処理を行なった後の判定でよければ、以下のように RemoteProviderException をハンドリングし、Unable to write data という文字列が返ってきたらディスクの空き容量がないと判定して良いそうです。

string GetDownloadError(AsyncOperationHandle fromHandle)
{
    if (fromHandle.Status != AsyncOperationStatus.Failed)
        return null;
 
    RemoteProviderException remoteException;
    Exception e = fromHandle.OperationException;
    while (e != null)
    {
        remoteException = e as RemoteProviderException;
        if (remoteException != null)
            return remoteException.WebRequestResult.Error;
        e = e.InnerException;
    }
    return null;
}

ちなみに上記のコードは Addressable アセットシステムにおけるダウンロードエラーのハンドリングの方法になりますが、これについての詳細は以下の記事にまとめています。

light11.hatenadiary.com

キャッシュの容量チェックとその難しさ

さて、Addressables ではデフォルトでダウンロードのために UnityWebRequest が使用されます。
これは内部的に Unity の Caching と呼ばれる仕組みを利用しており、容量がいっぱいになったら古いものから自動的に削除して容量を空けます。

この自動削除の仕組みがある以上、前節の方法でエラーが出るのは単純にダウンロードサイズが大きすぎるというわけではなさそうです。
アセットバンドルを LZMA 圧縮している場合には、UnityWebRequest がダウンロードをする際に LZ4 に圧縮し直して容量が大きくなるので、この関係なのではないかとスレッド内では言われています。

また、spaceFreeのドキュメントを見るとわかると通り、Unity の Caching システムはそもそもストレージの容量を見ているわけではありません。

これらのことから、ストレージの容量を事前に完璧にチェックするのは不可能だという前提を置きつつ、もし容量チェックをしたい場合には以下のようにすることが勧められています。

/// <summary>
/// Only checks against cache space and is not the same as disk space - see https://docs.unity3d.com/2021.2/Documentation/ScriptReference/Cache-spaceFree.html
/// </summary>
/// <param name="key">Addressables key to get download size for</param>
/// <returns>true if there is valid space</returns>
bool PredetermineCanDownload(string key)
{
    var op = Addressables.GetDownloadSizeAsync(key);
    op.WaitForCompletion();
    long downloadSize = op.Result;
    Addressables.Release(op);
    // could use OS methods to get the disk space here if needed
    if (Caching.currentCacheForWriting.spaceFree < downloadSize)
        return false;
    return true;
}

関連

light11.hatenadiary.com