LoginSignup
9
6

More than 5 years have passed since last update.

PhotonCloudのイベント処理をTaskで扱えるようにした

Posted at

今回の話

【PUN】PhotonCloudのコールバック処理をUniRxでスマートに書く「PhotonRx」 の続きの話です。

やったこと

PhotonCloudのサーバに接続する処理や、部屋に参加する処理をTaskで管理できるようにしました。

PUNのイベントをHookするコンポーネントを用意して、そこからTaskCompletionSourceを介してTaskを取得できるようにしています。

ログインイベントの取得を行うコンポーネント
public class ConnectionEventHook : MonoBehaviour
{
    private object gate = new object();

    private List<TaskCompletionSource<IResult<DisconnectCause, bool>>> observers
        = new List<TaskCompletionSource<IResult<DisconnectCause, bool>>>();

    public Task<IResult<DisconnectCause, bool>> Connect(Action connectAction)
    {
        var tcs = new TaskCompletionSource<IResult<DisconnectCause, bool>>();
        lock (gate)
        {
            observers.Add(tcs);
        }
        connectAction();
        return tcs.Task;
    }

    private void OnConnectedToPhoton()
    {
        if (PhotonNetwork.autoJoinLobby) return;
        lock (gate)
        {
            var targets = observers.ToArray();
            observers.Clear();
            foreach (var t in targets)
            {
                t.SetResult(Success.Create<DisconnectCause, bool>(true));
            }
        }
    }

    private void OnJoinedLobby()
    {
        if (!PhotonNetwork.autoJoinLobby) return;
        lock (gate)
        {
            var targets = observers.ToArray();
            observers.Clear();
            foreach (var t in targets)
            {
                t.SetResult(Success.Create<DisconnectCause, bool>(true));
            }
        }
    }

    private void OnFailedToConnectToPhoton(DisconnectCause cause)
    {
        lock (gate)
        {
            var targets = observers.ToArray();
            observers.Clear();
            foreach (var t in targets)
            {
                t.SetResult(Failure.Create<DisconnectCause, bool>(cause));
            }
        }
    }
}

これをstaticメソッド経由で呼び出せるようにしています。


public static class PhotoTask
{

    public static Task<IResult<DisconnectCause, bool>> ConnectToBestCloudServer(string gameVersion)
    {
        return Connect(() => PhotonNetwork.ConnectToBestCloudServer(gameVersion));
    }

    public static Task<IResult<DisconnectCause, bool>> ConnectToMaster(string masterServerAddress, int port, string appID, string gameVersion)
    {
        return Connect(() => PhotonNetwork.ConnectToMaster(masterServerAddress, port, appID, gameVersion));
    }

    public static Task<IResult<DisconnectCause, bool>> ConnectToRegion(CloudRegionCode region, string gameVersion)
    {
        return Connect(() => PhotonNetwork.ConnectToRegion(region, gameVersion));
    }

    public static Task<IResult<DisconnectCause, bool>> ConnectUsingSettings(string gameVersion)
    {
        return Connect(() => PhotonNetwork.ConnectUsingSettings(gameVersion));
    }

    private static Task<IResult<DisconnectCause, bool>> Connect(Action connectAction)
    {
        var eventHook = GetOrAddComponent<ConnectionEventHook>(PhotonEventManager.Instance.gameObject);
        return eventHook.Connect(connectAction);
    }

    //以下略
}

導入方法

PhotonRx】に追加機能としていれたので、こちらのライブラリを導入してください。

使用例

using UnityEngine;
using System.Threading.Tasks;
using PhotonRx;

public class TaskSample : MonoBehaviour
{
    async void Start()
    {
        PhotonNetwork.autoJoinLobby = true;

        var isConnected = await Connect();

        if (!isConnected) return;

        var isJoined = await JoinRoom();

        Debug.Log(isJoined);

    }

    private async Task<bool> Connect()
    {
        // サーバに接続
        var connect = await PhotoTask.ConnectUsingSettings("v1");

        if (connect.IsFailure)
        {
            // 失敗
            Debug.LogError(connect.ToFailure.Value);
        }

        return connect.IsSuccess;
    }

    private async Task<bool> JoinRoom()
    {
        // 適当な部屋に参加する
        var randomJoined = await PhotoTask.JoinRandomRoom();

        // 成功なら終わり
        if (randomJoined.IsSuccess) return true;

        // 部屋を作って参加する
        var created = await PhotoTask.CreateRoom("test", null, null, null);

        if (!created.IsSuccess)
        {
            //失敗
            Debug.LogError(created.ToFailure.Value);
        }

        return created.IsSuccess;
    }
}

Taskの結果として利用しているIResult型について

Taskの返り値としてオリジナルのIResult型を利用しています。

public static Task<IResult<DisconnectCause, bool>> ConnectUsingSettings(string gameVersion)

public static Task<IResult<FailureReason, bool>> JoinRoom(string roomName)

こちらは関数型プログラミング言語におけるEither型っぽいものを目指して作ったものです。
成功または失敗のどちらかを取る型であり、IsSuccessまたはIsFailureで成否の判定が行なえます。

/// <summary>
/// 成功か失敗かどちらかの状態を表す
/// </summary>
/// <typeparam name="L">失敗時の型</typeparam>
/// <typeparam name="R">成功時の型</typeparam>
public interface IResult<L, R>
{
    bool IsSuccess { get; }
    bool IsFailure { get; }

    Success<L, R> ToSuccess { get; }
    Failure<L, R> ToFailure { get; }

    IResult<L2, R2> Bind<L2, R2>(Func<L, IResult<L2, R2>> fl, Func<R, IResult<L2, R2>> fr);

    IResult<L, R> AsResult { get; }
}

あとなんとなくmapfaltMapも実装してみましたけど、たぶん使うことは無いでしょう。

参考

まとめ

PUNのイベントはさすがに公式でTask化してほしい。

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6