うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】Future パターンを実装してみる

Future パターンとは

wikipedia によると

何らかの処理を別のスレッドで処理させる際、その処理結果の取得を必要になるところまで後回しにする手法。

とある

例えばあるオブジェクトをロードする際にロード処理を丸投げしてしまい
使うときに問題なければ使うといった実装が Future パターンになる

UnityEngine の Future パターン

Unity 内では UnityWebRequest が Future パターンが実装されているものを挙げられる

UnityWebRequest のリクエストの流れを簡単に記述すると以下になる

// リクエスト用のインスタンスを作成
var request = UnityWebRequest.Get("http://www.my-server.com");
 
// リクエストを投げる
request.SendWebRequest()
  
// エラーや処理が終わってなければ使わない
if (!request.isDone || request.isNetworkError)
{
     return; 
}
 
// 成功していればロードする
if (request.isDone)
{
     Debug.Log(request.downloadHandler.text);
}

これは リクエストさえ投げてしまえば、リクエストの結果を使うタイミングはこちらで決めることができる

さらに、もし使うときにも、エラーや処理が終わってなければ待つことができるため、これは Future パターンと言えるはず

Future パターンを実装する

UnityWebRequest を元に最低限必要な機能を出してみる

  • Load : ロード処理を行う
  • IsError : エラーが発生したかどうか
  • Error : エラー時のメッセージ
  • IsDone : ロードが終了したかどうか
  • Value : ロード成功時に取得するおぶじぇくと

これを interface にするとこのようになる

public interface IFuture
{
    void Load();
}
  
public interface IFuture<T> : IFuture
{
    T Value { get; }
}

Value だけは型の指定が必要なので、型がいらない部分と、必要な部分で interface を分離

これで型が分からなくてもロード処理をできるようになる

これをクラス化するとこうなる

public abstract class Future<T> : IFuture<T>
{
    public abstract void Load();

    public bool IsError { get; protected set; }
    public bool IsDone => Value != null;
    public string Error { get; protected set; }

    public T Value => _Value;
    protected T _Value;
}

このクラスを元に、Resource をロードするクラスを作ってみると

public class ResourceRequest<T> : Future<T> where T : UnityEngine.Object
{
    private readonly string _path;
    public ResourceRequest(string path)
    {
        _path = path;
    }
 
    public override void Load()
    {
        var obj = Resources.Load(_path);
        _Value = obj as T;
        if (obj == null || Value == null)
        {
            IsError = true;
            Error = obj == null ? "Path is Illegal. " + _path : "Object Type is Illegal. " + nameof(T);
        }
    }
}

また、上記クラスを使ってロードするクラスがこちら

public static class ResourceLoad
{
    public static ResourceRequest<T> Load<T>(string path) where T : UnityEngine.Object
    {
        return new ResourceRequest<T>(path);
    }
}

実際にロード処理を書いてみるとこうなる

var request = ResourceLoad.Load<Sprite>("Hoge/Fuga");
request.Load();

if (request.IsDone)
{
    Debug.Log(request.Value.name);
}

個人的にはこれで、Future パターンが実装できたように思う