Initialer Upload neues Unity-Projekt
This commit is contained in:
539
Assets/Oculus/VR/Scripts/Util/Async/OVRTask.cs
Normal file
539
Assets/Oculus/VR/Scripts/Util/Async/OVRTask.cs
Normal file
@ -0,0 +1,539 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Oculus SDK License Agreement (the "License");
|
||||
* you may not use the Oculus SDK except in compliance with the License,
|
||||
* which is provided at the time of installation or download, or which
|
||||
* otherwise accompanies this software in either electronic or hard copy form.
|
||||
*
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://developer.oculus.com/licenses/oculussdk/
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
internal static class OVRTask
|
||||
{
|
||||
internal static OVRTask<TResult> FromGuid<TResult>(Guid id) => Create<TResult>(id);
|
||||
internal static OVRTask<TResult> FromRequest<TResult>(ulong id) => Create<TResult>(GetId(id));
|
||||
|
||||
internal static OVRTask<TResult> FromResult<TResult>(TResult result)
|
||||
{
|
||||
var task = Create<TResult>(Guid.NewGuid());
|
||||
task.SetResult(result);
|
||||
return task;
|
||||
}
|
||||
|
||||
internal static OVRTask<TResult> GetExisting<TResult>(Guid id) => Get<TResult>(id);
|
||||
internal static OVRTask<TResult> GetExisting<TResult>(ulong id) => Get<TResult>(GetId(id));
|
||||
|
||||
internal static void SetResult<TResult>(Guid id, TResult result) =>
|
||||
GetExisting<TResult>(id).SetResult(result);
|
||||
|
||||
internal static void SetResult<TResult>(ulong id, TResult result) =>
|
||||
GetExisting<TResult>(id).SetResult(result);
|
||||
|
||||
private static OVRTask<TResult> Get<TResult>(Guid id)
|
||||
{
|
||||
return new OVRTask<TResult>(id);
|
||||
}
|
||||
|
||||
private static OVRTask<TResult> Create<TResult>(Guid id)
|
||||
{
|
||||
var task = Get<TResult>(id);
|
||||
task.AddToPending();
|
||||
return task;
|
||||
}
|
||||
|
||||
internal static unsafe Guid GetId(ulong value)
|
||||
{
|
||||
const ulong hashModifier1 = 0x319642b2d24d8ec3;
|
||||
const ulong hashModifier2 = 0x96de1b173f119089;
|
||||
var guid = default(Guid);
|
||||
*(ulong*)&guid = unchecked(value + hashModifier1);
|
||||
*((ulong*)&guid + 1) = hashModifier2;
|
||||
return guid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an awaitable task.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a task-like object which supports the <c>await</c> pattern. Typically, you do not need to
|
||||
/// create or use this object directly. Instead, you can either :
|
||||
/// <para>- <c>await</c> a method which returns an object of type <see cref="OVRTask{TResult}"/>,
|
||||
/// which will eventually return a <typeparamref name="TResult"/></para>
|
||||
/// <para>- poll the <see cref="IsCompleted"/> property and then call <see cref="GetResult"/></para>
|
||||
/// <para>- pass a delegate by calling <see cref="ContinueWith(Action{TResult})"/>. Note that an additional state <c>object</c> can get passed in and added as a parameter of the callback, see <see cref="ContinueWith{T}"/></para>
|
||||
/// Requires the main thread to complete the await contract - blocking can result in an infinite loop.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TResult">The type of result being awaited.</typeparam>
|
||||
public readonly struct OVRTask<TResult> : IEquatable<OVRTask<TResult>>, IDisposable
|
||||
{
|
||||
#region static
|
||||
|
||||
private static readonly HashSet<Guid> Pending = new HashSet<Guid>();
|
||||
private static readonly Dictionary<Guid, TResult> Results = new Dictionary<Guid, TResult>();
|
||||
private static readonly Dictionary<Guid, Action> Continuations = new Dictionary<Guid, Action>();
|
||||
|
||||
private delegate void CallbackInvoker(Guid guid, TResult result);
|
||||
|
||||
private delegate bool CallbackRemover(Guid guid);
|
||||
|
||||
private static readonly Dictionary<Guid, CallbackInvoker>
|
||||
CallbackInvokers = new Dictionary<Guid, CallbackInvoker>();
|
||||
|
||||
private static readonly Dictionary<Guid, CallbackRemover>
|
||||
CallbackRemovers = new Dictionary<Guid, CallbackRemover>();
|
||||
|
||||
private static readonly HashSet<Action> CallbackClearers = new HashSet<Action>();
|
||||
|
||||
private delegate bool InternalDataRemover(Guid guid);
|
||||
|
||||
private static readonly Dictionary<Guid, InternalDataRemover> InternalDataRemovers =
|
||||
new Dictionary<Guid, InternalDataRemover>();
|
||||
|
||||
private static readonly HashSet<Action> InternalDataClearers = new HashSet<Action>();
|
||||
|
||||
private static readonly Dictionary<Guid, Action<Guid>> SubscriberRemovers =
|
||||
new Dictionary<Guid, Action<Guid>>();
|
||||
|
||||
private static readonly HashSet<Action> SubscriberClearers = new HashSet<Action>();
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly Guid _id;
|
||||
|
||||
internal OVRTask(Guid id)
|
||||
{
|
||||
_id = id;
|
||||
}
|
||||
|
||||
internal void AddToPending() => Pending.Add(_id);
|
||||
internal bool IsPending => Pending.Contains(_id);
|
||||
internal void SetInternalData<T>(T data) => InternalData<T>.Set(_id, data);
|
||||
internal bool TryGetInternalData<T>(out T data) => InternalData<T>.TryGet(_id, out data);
|
||||
|
||||
internal void SetResult(TResult result)
|
||||
{
|
||||
// Means no one was awaiting this result.
|
||||
if (!Pending.Remove(_id)) return;
|
||||
|
||||
if (InternalDataRemovers.TryGetValue(_id, out var internalDataRemover))
|
||||
{
|
||||
InternalDataRemovers.Remove(_id);
|
||||
internalDataRemover(_id);
|
||||
}
|
||||
|
||||
if (SubscriberRemovers.TryGetValue(_id, out var subscriberRemover))
|
||||
{
|
||||
SubscriberRemovers.Remove(_id);
|
||||
subscriberRemover(_id);
|
||||
}
|
||||
|
||||
if (CallbackInvokers.TryGetValue(_id, out var invoker))
|
||||
{
|
||||
CallbackInvokers.Remove(_id);
|
||||
invoker(_id, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add to the results so that GetResult can retrieve it later.
|
||||
Results.Add(_id, result);
|
||||
|
||||
if (Continuations.TryGetValue(_id, out var continuation))
|
||||
{
|
||||
Continuations.Remove(_id);
|
||||
continuation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class InternalData<T>
|
||||
{
|
||||
private static readonly Dictionary<Guid, T> Data = new Dictionary<Guid, T>();
|
||||
|
||||
public static bool TryGet(Guid taskId, out T data)
|
||||
{
|
||||
return Data.TryGetValue(taskId, out data);
|
||||
}
|
||||
|
||||
public static void Set(Guid taskId, T data)
|
||||
{
|
||||
Data[taskId] = data;
|
||||
InternalDataRemovers.Add(taskId, Remover);
|
||||
InternalDataClearers.Add(Clearer);
|
||||
}
|
||||
|
||||
private static readonly InternalDataRemover Remover = Remove;
|
||||
private static readonly Action Clearer = Clear;
|
||||
private static bool Remove(Guid taskId) => Data.Remove(taskId);
|
||||
private static void Clear() => Data.Clear();
|
||||
}
|
||||
|
||||
static class IncrementalResultSubscriber<T>
|
||||
{
|
||||
static readonly Dictionary<Guid, Action<T>> Subscribers = new Dictionary<Guid, Action<T>>();
|
||||
|
||||
public static void Set(Guid taskId, Action<T> subscriber)
|
||||
{
|
||||
Subscribers[taskId] = subscriber;
|
||||
SubscriberRemovers[taskId] = Remover;
|
||||
SubscriberClearers.Add(Clearer);
|
||||
}
|
||||
|
||||
public static void Notify(Guid taskId, T result)
|
||||
{
|
||||
if (Subscribers.TryGetValue(taskId, out var subscriber))
|
||||
{
|
||||
subscriber(result);
|
||||
}
|
||||
}
|
||||
|
||||
static readonly Action<Guid> Remover = Remove;
|
||||
|
||||
static void Remove(Guid id) => Subscribers.Remove(id);
|
||||
|
||||
static readonly Action Clearer = Clear;
|
||||
|
||||
static void Clear() => Subscribers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the delegate to be invoked when an incremental result is available before the task is complete.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Some tasks may provide incremental results before the task is complete. In this case, you can use
|
||||
/// <see cref="SetIncrementalResultCallback{TIncrementalResult}"/> to receive those results as they become available.
|
||||
///
|
||||
/// For example, the task may provide a list of results over some period of time and may be able to provide
|
||||
/// partial results as they become available, before the task completes.
|
||||
/// </remarks>
|
||||
/// <param name="onIncrementalResultAvailable">Invoked whenever <see cref="NotifyIncrementalResult{TIncrementalResult}"/>
|
||||
/// is called.</param>
|
||||
/// <typeparam name="TIncrementalResult">The type of the incremental result. This is typically different than the
|
||||
/// <typeparamref name="TResult"/>.</typeparam>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="onIncrementalResultAvailable"/> is `null`.</exception>
|
||||
internal void SetIncrementalResultCallback<TIncrementalResult>(
|
||||
Action<TIncrementalResult> onIncrementalResultAvailable)
|
||||
{
|
||||
if (onIncrementalResultAvailable == null)
|
||||
throw new ArgumentNullException(nameof(onIncrementalResultAvailable));
|
||||
|
||||
IncrementalResultSubscriber<TIncrementalResult>.Set(_id, onIncrementalResultAvailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies a subscriber of an incremental result associated with an ongoing task.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use this to provide partial results that may be available before the task fully completes.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TIncrementalResult">The type of the result, usually different from <typeparamref name="TResult"/>.</typeparam>
|
||||
internal void NotifyIncrementalResult<TIncrementalResult>(TIncrementalResult incrementalResult)
|
||||
=> IncrementalResultSubscriber<TIncrementalResult>.Notify(_id, incrementalResult);
|
||||
|
||||
#region Polling Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the task has completed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Choose only one pattern out of the three proposed way of awaiting for the task completion:
|
||||
/// Polling,<c>async/await</c> or <see cref="ContinueWith(Action{TResult})"/>
|
||||
/// as all three patterns will end up calling the <see cref="GetResult"/> which can only be called once.
|
||||
/// </remarks>
|
||||
/// <returns><c>True</c> if the task has completed. <see cref="GetResult"/> can be called.</returns>
|
||||
public bool IsCompleted => !IsPending;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result of the Task.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method should only be called once <see cref="IsCompleted"/> is true.
|
||||
/// Calling it multiple times leads to undefined behavior.
|
||||
/// Do not use in conjunction with any other methods (<c>await</c> or using <see cref="ContinueWith"/>).
|
||||
/// </remarks>
|
||||
/// <returns>Returns the result of type <typeparamref name="TResult"/>.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the task doesn't have any available result. This could
|
||||
/// happen if the method is called before <see cref="IsCompleted"/> is true, after the task has been disposed of
|
||||
/// or if this method has already been called once.</exception>
|
||||
public TResult GetResult()
|
||||
{
|
||||
if (!Results.TryGetValue(_id, out var value))
|
||||
{
|
||||
throw new InvalidOperationException($"Task {_id} doesn't have any available result.");
|
||||
}
|
||||
|
||||
Results.Remove(_id);
|
||||
return value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Awaiter Contract Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Definition of an awaiter that satisfies the await contract.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This allows an <see cref="OVRTask{T}"/> to be awaited using the <c>await</c> keyword.
|
||||
/// Typically, you should not use this struct; instead, it is used by the compiler by
|
||||
/// automatically calling the <see cref="GetAwaiter"/> method when using the <c>await</c> keyword.
|
||||
/// </remarks>
|
||||
public readonly struct Awaiter : INotifyCompletion
|
||||
{
|
||||
private readonly OVRTask<TResult> _task;
|
||||
|
||||
internal Awaiter(OVRTask<TResult> task)
|
||||
{
|
||||
_task = task;
|
||||
}
|
||||
|
||||
public bool IsCompleted => _task.IsCompleted;
|
||||
public void OnCompleted(Action continuation) => _task.WithContinuation(continuation);
|
||||
public TResult GetResult() => _task.GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an awaiter that satisfies the await contract.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This allows an <see cref="OVRTask{T}"/> to be awaited using the <c>await</c> keyword.
|
||||
/// Typically, you should not call this directly; instead, it is invoked by the compiler, e.g.,
|
||||
/// <example>
|
||||
/// <code><![CDATA[
|
||||
/// // Something that returns an OVRTask<T>
|
||||
/// var task = GetResultAsync();
|
||||
///
|
||||
/// // compiler uses GetAwaiter here
|
||||
/// var result = await task;
|
||||
/// ]]></code>
|
||||
/// Or, more commonly:
|
||||
/// <code><![CDATA[
|
||||
/// var result = await GetResultAsync();
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
/// Requires the main thread to complete the await contract - blocking can result in an infinite loop.
|
||||
/// </remarks>
|
||||
/// <returns>Returns an Awaiter-like object that satisfies the await pattern.</returns>
|
||||
public Awaiter GetAwaiter() => new Awaiter(this);
|
||||
|
||||
private void WithContinuation(Action continuation)
|
||||
{
|
||||
ValidateDelegateAndThrow(continuation, nameof(continuation));
|
||||
|
||||
Continuations[_id] = continuation;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delegate Implementation
|
||||
|
||||
readonly struct Callback
|
||||
{
|
||||
private static readonly Dictionary<Guid, Callback> Callbacks = new Dictionary<Guid, Callback>();
|
||||
|
||||
readonly Action<TResult> _delegate;
|
||||
|
||||
static void Invoke(Guid taskId, TResult result)
|
||||
{
|
||||
if (Callbacks.TryGetValue(taskId, out var callback))
|
||||
{
|
||||
Callbacks.Remove(taskId);
|
||||
callback.Invoke(result);
|
||||
}
|
||||
}
|
||||
|
||||
static bool Remove(Guid taskId) => Callbacks.Remove(taskId);
|
||||
|
||||
static void Clear() => Callbacks.Clear();
|
||||
|
||||
void Invoke(TResult result) => _delegate(result);
|
||||
|
||||
Callback(Action<TResult> @delegate) => _delegate = @delegate;
|
||||
|
||||
public static readonly CallbackInvoker Invoker = Invoke;
|
||||
|
||||
public static readonly CallbackRemover Remover = Remove;
|
||||
|
||||
public static readonly Action Clearer = Clear;
|
||||
|
||||
public static void Add(Guid taskId, Action<TResult> @delegate)
|
||||
{
|
||||
Callbacks.Add(taskId, new Callback(@delegate));
|
||||
CallbackInvokers.Add(taskId, Invoker);
|
||||
CallbackRemovers.Add(taskId, Remover);
|
||||
CallbackClearers.Add(Clearer);
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct CallbackWithState<T>
|
||||
{
|
||||
private static readonly Dictionary<Guid, CallbackWithState<T>> Callbacks =
|
||||
new Dictionary<Guid, CallbackWithState<T>>();
|
||||
|
||||
readonly T _data;
|
||||
|
||||
readonly Action<TResult, T> _delegate;
|
||||
|
||||
static void Invoke(Guid taskId, TResult result)
|
||||
{
|
||||
if (Callbacks.TryGetValue(taskId, out var callback))
|
||||
{
|
||||
Callbacks.Remove(taskId);
|
||||
callback.Invoke(result);
|
||||
}
|
||||
}
|
||||
|
||||
CallbackWithState(T data, Action<TResult, T> @delegate)
|
||||
{
|
||||
_data = data;
|
||||
_delegate = @delegate;
|
||||
}
|
||||
|
||||
private static readonly CallbackInvoker Invoker = Invoke;
|
||||
private static readonly CallbackRemover Remover = Remove;
|
||||
private static readonly Action Clearer = Clear;
|
||||
private static void Clear() => Callbacks.Clear();
|
||||
private static bool Remove(Guid taskId) => Callbacks.Remove(taskId);
|
||||
private void Invoke(TResult result) => _delegate(result, _data);
|
||||
|
||||
public static void Add(Guid taskId, T data, Action<TResult, T> callback)
|
||||
{
|
||||
Callbacks.Add(taskId, new CallbackWithState<T>(data, callback));
|
||||
CallbackInvokers.Add(taskId, Invoker);
|
||||
CallbackRemovers.Add(taskId, Remover);
|
||||
CallbackClearers.Add(Clearer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a delegate that will get called on completion of the task.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The delegate will be invoked with the <typeparamref name="TResult"/> result as parameter.
|
||||
/// Do not use in conjunction with any other methods (<c>await</c> or calling <see cref="GetResult"/>).
|
||||
/// </remarks>
|
||||
/// <param name="onCompleted">A delegate to be invoked when this task completes. If the task is already complete,
|
||||
/// <paramref name="onCompleted"/> is invoked immediately.</param>
|
||||
/// <seealso cref="ContinueWith{T}"/>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="onCompleted"/> is null.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown if there already is a delegate or a continuation registered to this task.</exception>
|
||||
public void ContinueWith(Action<TResult> onCompleted)
|
||||
{
|
||||
ValidateDelegateAndThrow(onCompleted, nameof(onCompleted));
|
||||
|
||||
if (IsCompleted)
|
||||
{
|
||||
onCompleted.Invoke(GetResult());
|
||||
}
|
||||
else
|
||||
{
|
||||
Callback.Add(_id, onCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a delegate that will get called on completion of the task.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The delegate will be invoked with <paramref name="state"/> and the <typeparamref name="TResult"/> result as
|
||||
/// parameters.
|
||||
/// Do not use in conjunction with any other methods (<c>await</c> or calling <see cref="GetResult"/>).
|
||||
/// </remarks>
|
||||
/// <param name="onCompleted">A delegate to be invoked when this task completes. If the task is already complete,
|
||||
/// <paramref name="onCompleted"/> is invoked immediately.</param>
|
||||
/// <param name="state">An <c>object</c> to store and pass to <paramref name="onCompleted"/>.</param>
|
||||
/// <seealso cref="ContinueWith(Action{TResult})"/>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="onCompleted"/> is null.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown if there already is a delegate or a continuation registered to this task.</exception>
|
||||
public void ContinueWith<T>(Action<TResult, T> onCompleted, T state)
|
||||
{
|
||||
ValidateDelegateAndThrow(onCompleted, nameof(onCompleted));
|
||||
|
||||
if (IsCompleted)
|
||||
{
|
||||
onCompleted.Invoke(GetResult(), state);
|
||||
}
|
||||
else
|
||||
{
|
||||
CallbackWithState<T>.Add(_id, state, onCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
void ValidateDelegateAndThrow(object @delegate, string paramName)
|
||||
{
|
||||
if (@delegate == null)
|
||||
throw new ArgumentNullException(paramName);
|
||||
|
||||
if (Continuations.ContainsKey(_id))
|
||||
throw new InvalidOperationException($"Task {_id} is already being used by an await call.");
|
||||
|
||||
if (CallbackInvokers.ContainsKey(_id))
|
||||
throw new InvalidOperationException($"Task {_id} is already being used with ContinueWith.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the task.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Invalidate this object but does not cancel the task.
|
||||
/// In the case where the result will not actually be consumed, it must be called to prevent a memory leak.
|
||||
/// You can not call <see cref="GetResult"/> nor use <c>await</c> on a disposed task.
|
||||
/// </remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
Results.Remove(_id);
|
||||
Continuations.Remove(_id);
|
||||
Pending.Remove(_id);
|
||||
|
||||
CallbackInvokers.Remove(_id);
|
||||
if (CallbackRemovers.TryGetValue(_id, out var remover))
|
||||
{
|
||||
CallbackRemovers.Remove(_id);
|
||||
remover(_id);
|
||||
}
|
||||
|
||||
if (InternalDataRemovers.TryGetValue(_id, out var internalDataRemover))
|
||||
{
|
||||
InternalDataRemovers.Remove(_id);
|
||||
internalDataRemover(_id);
|
||||
}
|
||||
|
||||
if (SubscriberRemovers.TryGetValue(_id, out var subscriberRemover))
|
||||
{
|
||||
SubscriberRemovers.Remove(_id);
|
||||
subscriberRemover(_id);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEquatable Implementation
|
||||
|
||||
public bool Equals(OVRTask<TResult> other) => _id == other._id;
|
||||
public override bool Equals(object obj) => obj is OVRTask<TResult> other && Equals(other);
|
||||
public static bool operator ==(OVRTask<TResult> lhs, OVRTask<TResult> rhs) => lhs.Equals(rhs);
|
||||
public static bool operator !=(OVRTask<TResult> lhs, OVRTask<TResult> rhs) => !lhs.Equals(rhs);
|
||||
public override int GetHashCode() => _id.GetHashCode();
|
||||
public override string ToString() => _id.ToString();
|
||||
|
||||
#endregion
|
||||
}
|
||||
3
Assets/Oculus/VR/Scripts/Util/Async/OVRTask.cs.meta
Normal file
3
Assets/Oculus/VR/Scripts/Util/Async/OVRTask.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bc71515869d46e08e210173388da05c
|
||||
timeCreated: 1669731273
|
||||
Reference in New Issue
Block a user