0
Help us understand the problem. What are the problem?

posted at

updated at

Organization

Oculus Platform APIをコールバックからTaskへ変換する

はじめに

Oculus Storeからアプリを配布する時はOculus Platform APIが必須になっています。
そのOculus Platform APIは結果をコールバックで返すので処理が冗長になってしまいますが簡単にTask化できたので紹介します。

開発環境

  • System.Threading.Tasksを扱える設定およびバージョンのUnity
    • Unity2017 + Experimental (.NET 4.6 Equivalent)
    • Unity2018 + .NET 4.x Equivalent
    • Unity2019以上
  • Oculus Integration v1.41.0以上

コールバックの場合

以下はエンタイトルメントチェックのベストプラクティスのサンプルコードです。Oculus Platform APIはIsUserEntitledToApplication()のようにOnComplete()で結果が返ってくるので同様の呼び出しが続くとコールバック地獄になって処理の流れが分かりにくくなります。

using UnityEngine;
using Oculus.Platform;

public class AppEntitlementCheck: MonoBehaviour {

  void Awake ()
  {
    try
    {
      Core.AsyncInitialize();
      Entitlements.IsUserEntitledToApplication().OnComplete(EntitlementCallback); // ここがコールバック
    }
    catch(UnityException e)
    {
      Debug.LogError("Platform failed to initialize due to exception.");
      Debug.LogException(e);
      // Immediately quit the application.
      UnityEngine.Application.Quit();
    }
  }

  void EntitlementCallback (Message msg)
  {
    if (msg.IsError)
    {
      Debug.LogError("You are NOT entitled to use this app.");
      UnityEngine.Application.Quit();
    }
    else
    {
      Debug.Log("You are entitled to use this app.");
    }
  }
}

Taskへ変換する

スクリプティング定義シンボルにOVR_PLATFORM_ASYNC_MESSAGESを追加します。
image.png

するとTaskを返すGen()メソッドがOculus.Platform.Requestクラスで使えるようになるので以下のように書く事ができます。

private async void Awake()
{
    try
    {
        if (!Core.Initialized())
        {
            var initialized = await Core.AsyncInitialize().Gen(); // ここをTask化
            if (initialized.IsError)
            {
                Debug.Log($"failed initialize: {initialized.GetError().Message}");
                return;
            }
        }

        var entitlements = await Entitlements.IsUserEntitledToApplication().Gen(); // ここをTask化
        if (entitlements.IsError)
        {
            Debug.Log($"failed entitlement: {entitlements.GetError().Message}");
            return;
        }

        var user = await Users.GetLoggedInUser().Gen(); // ここをTask化
        if (user.IsError)
        {
            Debug.Log($"failed get user: {user.GetError().Message}");
            return;
        }
        Debug.Log($"{user.Data.ID}, {user.Data.OculusID}");
    }
    catch (UnityException e)
    {
        Debug.LogException(e);
        Application.Quit();
    }
}

更にUniTaskと組み合わせるとタイムアウトも簡単に扱えます。

var cts = new CancellationTokenSource();
cts.CancelAfterSlim(TimeSpan.FromSeconds(10));
try
{
    var initialized = await Core.AsyncInitialize().Gen().AsUniTask().AttachExternalCancellation(cts.Token);
}
catch (OperationCanceledException e)
{
    if (ex.CancellationToken == cts.Token)
    {
        Debug.Log("Timeout");
    }
}

1つ残念なのはGen()が返すTaskTaskCompletionSourceで作られてprivateフィールドにあるのですがTrySetCanceled()を呼ぶメソッドが用意されていないのでCancellationTokenでキャンセルできない事です。そのため、UniTaskのタイムアウトなどと組み合わせるなどの工夫が必要です。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
0
Help us understand the problem. What are the problem?