Unity の WWW で画像のような比較的大きいデータを取得するとき、毎回データを取得しなおすのは非効率なので、 なんらかの方法でキャッシュしたい。

とりあえず雑に byte 配列をファイルに書き込んでキャッシュしたら、まあまあ良さそう :+1: な感じだった。

概要

  • WWW 結果の bytes(型は byte[])をファイルに書き込んでキャッシュする
  • キャッシュファイル名とリクエスト URL を 1:1 対応させて、キャッシュファイルを識別する
  • キャッシュの作成や削除は、コードで明示的に行う

環境

  • Unity 5.3.4p4

キャッシュの方針

  • とりあえず雑に
  • ファイルにキャッシュする
  • リクエスト URL をキーにして、キャッシュを一意に識別する
  • 時間を見て expire させたりはしない
  • キャッシュを読みに行くときは、正規の URL の代わりに file:// プロトコルの URL を読みに行く
  • キャッシュファイルは tmp に置いておいておいたら、いつか勝手に消えるかも
  • キャッシュファイルは自分の好きなタイミングで(も)消せるように

結果

雑に作成したクラスは以下のとおり。

WWWCache.cs

using UnityEngine;
using System.IO;

public static class WWWCache
{
    private static string CacheDirectory { get { return Path.Combine(Application.temporaryCachePath, "www"); } }

    public static bool HasCache(string key)
    {
        return File.Exists(GetCacheFilePath(key));
    }

    public static string GetCacheURL(string key)
    {
        return "file://" + GetCacheFilePath(key);
    }

    private static string GetCacheFilePath(string key)
    {
        var hashCode = key.GetHashCode().ToString();
        var cacheURL = Path.Combine(CacheDirectory, hashCode);

        return cacheURL;
    }

    public static void CreateCache(string key, byte[] bytes)
    {
        if (!Directory.Exists(CacheDirectory))
        {
            Directory.CreateDirectory(CacheDirectory);
        }

        File.WriteAllBytes(GetCacheFilePath(key), bytes);
    }

    public static void ClearCache()
    {
        if (!Directory.Exists(CacheDirectory))
        {
            return;
        }

        Directory.Delete(CacheDirectory, true);
    }
}

:bulb: 小さなポイントとしては、key そのものではなくハッシュ値を使っている。 key にリクエスト URL のようなファイルシステムのパスと競合する値を投げられても大丈夫なように。

使い方

WWWCache を使ってキャッシュをすると、以下のようになる。key にはリクエスト URL を使った。 同じ URL にリクエストを送ろうとした場合に、キャッシュがあればそちらを取得しに行く。

public IEnumerator SendRequest(string url)
{
    var requestURL = WWWCache.HasCache(url) ? WWWCache.GetCacheURL(url) : url;
    var www = new WWW(requestURL);
    yield return www;

    if (!string.IsNullOrEmpty(www.error))
    {
        yield break;
    }

    WWWCache.CreateCache(url, www.bytes);
}

:bulb: 毎回明示的にキャッシュ処理を書くのが嫌であれば、IEnumerator を実装したクラスで WWW を wrap してしまうとか。

まとめ・感想

  • WWW 結果の WWW#bytes(型は byte[])をファイルに書き込んでキャッシュした
  • キャッシュファイル名とリクエスト URL を 1:1 対応させて、キャッシュファイルを識別した
    • URL はファイルシステムのパスと競合するので、string#GetHashCode でハッシュ値にして使った
  • キャッシュの作成や削除は、コードで明示的に行った
    • 削除については、一括で削除するメソッドだけ作っておいた

とりあえず雑に作ってみたが、今のところまあまあ良さそうに動いている。

ただし、レスポンスから有効期限を読み取ったりしていないので、かなり適当なキャッシュではある :angel: あと、キャッシュの読み書きの度にファイルの I/O が発生するので、爆速 :rocket: とはいかない。

「有効期間は一括で 15 分のゆるふわ仕様」とかだったら、ファイルの作成時刻を読めばすぐできる気がする。

参考