mirror of
https://github.com/Cysharp/UniTask.git
synced 2026-05-19 21:50:08 +00:00
complete implementation(triggers and etc...)
This commit is contained in:
@@ -9,202 +9,47 @@ using UnityEngine;
|
||||
|
||||
namespace UniRx.Async.Triggers
|
||||
{
|
||||
public interface ICancelablePromise
|
||||
{
|
||||
CancellationToken RegisteredCancellationToken { get; }
|
||||
bool TrySetCanceled();
|
||||
}
|
||||
|
||||
|
||||
public interface ICancellationTokenKeyDictionary
|
||||
{
|
||||
void Remove(CancellationToken token);
|
||||
}
|
||||
|
||||
public class AsyncTriggerPromiseDictionary<TPromiseType> :
|
||||
Dictionary<CancellationToken, AutoResetUniTaskCompletionSource<TPromiseType>>,
|
||||
ICancellationTokenKeyDictionary,
|
||||
IEnumerable<ICancelablePromise>
|
||||
{
|
||||
public AsyncTriggerPromiseDictionary()
|
||||
: base(CancellationTokenEqualityComparer.Default)
|
||||
{
|
||||
}
|
||||
|
||||
IEnumerator<ICancelablePromise> IEnumerable<ICancelablePromise>.GetEnumerator()
|
||||
{
|
||||
// TODO:
|
||||
throw new NotImplementedException();
|
||||
//return Values.GetEnumerator();
|
||||
}
|
||||
|
||||
void ICancellationTokenKeyDictionary.Remove(CancellationToken token)
|
||||
{
|
||||
this.Remove(token);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AsyncTriggerBase : MonoBehaviour
|
||||
{
|
||||
static readonly Action<object> Callback = CancelCallback;
|
||||
|
||||
bool calledAwake = false;
|
||||
bool destroyCalled = false;
|
||||
CancellationTokenRegistration[] registeredCancellations;
|
||||
int registeredCancellationsCount;
|
||||
|
||||
protected abstract IEnumerable<ICancelablePromise> GetPromises();
|
||||
|
||||
static protected IEnumerable<ICancelablePromise> Concat(ICancelablePromise p1, IEnumerable<ICancelablePromise> p1s)
|
||||
{
|
||||
if (p1 != null) yield return p1;
|
||||
if (p1s != null) foreach (var item in p1s) yield return item;
|
||||
}
|
||||
|
||||
static protected IEnumerable<ICancelablePromise> Concat(
|
||||
ICancelablePromise p1, IEnumerable<ICancelablePromise> p1s,
|
||||
ICancelablePromise p2, IEnumerable<ICancelablePromise> p2s)
|
||||
{
|
||||
if (p1 != null) yield return p1;
|
||||
if (p1s != null) foreach (var item in p1s) yield return item;
|
||||
if (p2 != null) yield return p2;
|
||||
if (p2s != null) foreach (var item in p2s) yield return item;
|
||||
}
|
||||
|
||||
static protected IEnumerable<ICancelablePromise> Concat(
|
||||
ICancelablePromise p1, IEnumerable<ICancelablePromise> p1s,
|
||||
ICancelablePromise p2, IEnumerable<ICancelablePromise> p2s,
|
||||
ICancelablePromise p3, IEnumerable<ICancelablePromise> p3s)
|
||||
{
|
||||
if (p1 != null) yield return p1;
|
||||
if (p1s != null) foreach (var item in p1s) yield return item;
|
||||
if (p2 != null) yield return p2;
|
||||
if (p2s != null) foreach (var item in p2s) yield return item;
|
||||
if (p3 != null) yield return p3;
|
||||
if (p3s != null) foreach (var item in p3s) yield return item;
|
||||
}
|
||||
|
||||
static protected IEnumerable<ICancelablePromise> Concat(
|
||||
ICancelablePromise p1, IEnumerable<ICancelablePromise> p1s,
|
||||
ICancelablePromise p2, IEnumerable<ICancelablePromise> p2s,
|
||||
ICancelablePromise p3, IEnumerable<ICancelablePromise> p3s,
|
||||
ICancelablePromise p4, IEnumerable<ICancelablePromise> p4s)
|
||||
{
|
||||
if (p1 != null) yield return p1;
|
||||
if (p1s != null) foreach (var item in p1s) yield return item;
|
||||
if (p2 != null) yield return p2;
|
||||
if (p2s != null) foreach (var item in p2s) yield return item;
|
||||
if (p3 != null) yield return p3;
|
||||
if (p3s != null) foreach (var item in p3s) yield return item;
|
||||
if (p4 != null) yield return p4;
|
||||
if (p4s != null) foreach (var item in p4s) yield return item;
|
||||
}
|
||||
|
||||
// otherwise...
|
||||
static protected IEnumerable<ICancelablePromise> Concat(params object[] promises)
|
||||
{
|
||||
foreach (var item in promises)
|
||||
{
|
||||
if (item is ICancelablePromise p)
|
||||
{
|
||||
yield return p;
|
||||
}
|
||||
else if (item is IEnumerable<ICancelablePromise> ps)
|
||||
{
|
||||
foreach (var p2 in ps)
|
||||
{
|
||||
yield return p2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected UniTask<T> GetOrAddPromise<T>(ref AsyncTriggerPromise<T> promise, ref AsyncTriggerPromiseDictionary<T> promises, CancellationToken cancellationToken)
|
||||
{
|
||||
if (destroyCalled) return UniTask.FromCanceled<T>();
|
||||
|
||||
if (!cancellationToken.CanBeCanceled)
|
||||
{
|
||||
if (promise == null)
|
||||
{
|
||||
promise = new AsyncTriggerPromise<T>();
|
||||
if (!calledAwake)
|
||||
{
|
||||
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, new AwakeMonitor(this));
|
||||
}
|
||||
}
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
if (promises == null) promises = new AsyncTriggerPromiseDictionary<T>();
|
||||
|
||||
if (promises.TryGetValue(cancellationToken, out var cancellablePromise))
|
||||
{
|
||||
return cancellablePromise.Task;
|
||||
}
|
||||
|
||||
cancellablePromise = new AsyncTriggerPromise<T>();
|
||||
promises.Add(cancellationToken, cancellablePromise);
|
||||
if (!calledAwake)
|
||||
{
|
||||
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, new AwakeMonitor(this));
|
||||
}
|
||||
|
||||
var registrationToken = cancellationToken.RegisterWithoutCaptureExecutionContext(Callback, Tuple.Create((ICancellationTokenKeyDictionary)promises, (ICancelablePromise)cancellablePromise));
|
||||
if (registeredCancellations == null)
|
||||
{
|
||||
registeredCancellations = ArrayPool<CancellationTokenRegistration>.Shared.Rent(4);
|
||||
}
|
||||
ArrayPoolUtil.EnsureCapacity(ref registeredCancellations, registeredCancellationsCount + 1, ArrayPool<CancellationTokenRegistration>.Shared);
|
||||
registeredCancellations[registeredCancellationsCount++] = registrationToken;
|
||||
|
||||
return cancellablePromise.Task;
|
||||
}
|
||||
|
||||
static void CancelCallback(object state)
|
||||
{
|
||||
var tuple = (Tuple<ICancellationTokenKeyDictionary, ICancelablePromise>)state;
|
||||
var dict = tuple.Item1;
|
||||
var promise = tuple.Item2;
|
||||
|
||||
promise.TrySetCanceled();
|
||||
dict.Remove(promise.RegisteredCancellationToken);
|
||||
}
|
||||
|
||||
protected void TrySetResult<T>(ReusablePromise<T> promise, AsyncTriggerPromiseDictionary<T> promises, T value)
|
||||
{
|
||||
if (promise != null)
|
||||
{
|
||||
promise.TrySetResult(value);
|
||||
}
|
||||
if (promises != null)
|
||||
{
|
||||
PromiseHelper.TrySetResultAll(promises.Values, value);
|
||||
}
|
||||
}
|
||||
bool calledAwake;
|
||||
bool calledDestroy;
|
||||
ICancelPromise triggerSlot;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
calledAwake = true;
|
||||
}
|
||||
|
||||
protected TriggerEvent<T> SetTriggerEvent<T>(ref TriggerEvent<T> field)
|
||||
{
|
||||
if (field == null)
|
||||
{
|
||||
field = new TriggerEvent<T>();
|
||||
if (triggerSlot == null)
|
||||
{
|
||||
triggerSlot = field;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("triggerSlot is already filled.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!calledAwake)
|
||||
{
|
||||
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, new AwakeMonitor(this));
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (destroyCalled) return;
|
||||
destroyCalled = true;
|
||||
foreach (var item in GetPromises())
|
||||
{
|
||||
item.TrySetCanceled();
|
||||
}
|
||||
if (registeredCancellations != null)
|
||||
{
|
||||
for (int i = 0; i < registeredCancellationsCount; i++)
|
||||
{
|
||||
registeredCancellations[i].Dispose();
|
||||
registeredCancellations[i] = default(CancellationTokenRegistration);
|
||||
}
|
||||
ArrayPool<CancellationTokenRegistration>.Shared.Return(registeredCancellations);
|
||||
}
|
||||
if (calledDestroy) return;
|
||||
calledDestroy = true;
|
||||
|
||||
triggerSlot?.TrySetCanceled();
|
||||
triggerSlot = null;
|
||||
}
|
||||
|
||||
class AwakeMonitor : IPlayerLoopItem
|
||||
@@ -228,6 +73,313 @@ namespace UniRx.Async.Triggers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAsyncOneShotTrigger
|
||||
{
|
||||
UniTask OneShotAsync();
|
||||
}
|
||||
|
||||
public partial class AsyncTriggerHandler<T> : IAsyncOneShotTrigger
|
||||
{
|
||||
UniTask IAsyncOneShotTrigger.OneShotAsync()
|
||||
{
|
||||
core.Reset();
|
||||
return new UniTask((IUniTaskSource)this, core.Version);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class AsyncTriggerHandler<T> : IUniTaskSource<T>, IResolvePromise<T>, ICancelPromise, IDisposable
|
||||
{
|
||||
static Action<object> cancellationCallback = CancellationCallback;
|
||||
|
||||
readonly TriggerEvent<T> trigger;
|
||||
|
||||
CancellationToken cancellationToken;
|
||||
CancellationTokenRegistration registration;
|
||||
bool isDisposed;
|
||||
bool callOnce;
|
||||
|
||||
UniTaskCompletionSourceCore<T> core;
|
||||
|
||||
internal CancellationToken CancellationToken => cancellationToken;
|
||||
|
||||
public AsyncTriggerHandler(TriggerEvent<T> trigger, bool callOnce)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
isDisposed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.trigger = trigger;
|
||||
this.cancellationToken = default;
|
||||
this.registration = default;
|
||||
this.callOnce = callOnce;
|
||||
|
||||
trigger.Add(this);
|
||||
|
||||
TaskTracker.TrackActiveTask(this, 3);
|
||||
}
|
||||
|
||||
public AsyncTriggerHandler(TriggerEvent<T> trigger, CancellationToken cancellationToken, bool callOnce)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
isDisposed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.trigger = trigger;
|
||||
this.cancellationToken = cancellationToken;
|
||||
this.callOnce = callOnce;
|
||||
|
||||
trigger.Add(this);
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
registration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, this);
|
||||
}
|
||||
|
||||
TaskTracker.TrackActiveTask(this, 3);
|
||||
}
|
||||
|
||||
static void CancellationCallback(object state)
|
||||
{
|
||||
var self = (AsyncTriggerHandler<T>)state;
|
||||
self.Dispose();
|
||||
|
||||
self.core.TrySetCanceled(self.cancellationToken);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!isDisposed)
|
||||
{
|
||||
isDisposed = true;
|
||||
TaskTracker.RemoveTracking(this);
|
||||
registration.Dispose();
|
||||
trigger.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
T IUniTaskSource<T>.GetResult(short token)
|
||||
{
|
||||
try
|
||||
{
|
||||
return core.GetResult(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (callOnce)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IResolvePromise<T>.TrySetResult(T result)
|
||||
{
|
||||
return core.TrySetResult(result);
|
||||
}
|
||||
|
||||
bool ICancelPromise.TrySetCanceled(CancellationToken cancellationToken)
|
||||
{
|
||||
return core.TrySetCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
void IUniTaskSource.GetResult(short token)
|
||||
{
|
||||
((IUniTaskSource<T>)this).GetResult(token);
|
||||
}
|
||||
|
||||
UniTaskStatus IUniTaskSource.GetStatus(short token)
|
||||
{
|
||||
return core.GetStatus(token);
|
||||
}
|
||||
|
||||
UniTaskStatus IUniTaskSource.UnsafeGetStatus()
|
||||
{
|
||||
return core.UnsafeGetStatus();
|
||||
}
|
||||
|
||||
void IUniTaskSource.OnCompleted(Action<object> continuation, object state, short token)
|
||||
{
|
||||
core.OnCompleted(continuation, state, token);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TriggerEvent<T> : IResolvePromise<T>, ICancelPromise
|
||||
{
|
||||
// optimize: many cases, handler is single.
|
||||
AsyncTriggerHandler<T> singleHandler;
|
||||
|
||||
List<AsyncTriggerHandler<T>> handlers;
|
||||
|
||||
public bool TrySetResult(T value)
|
||||
{
|
||||
List<Exception> exceptions = null;
|
||||
|
||||
if (singleHandler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
((IResolvePromise<T>)singleHandler).TrySetResult(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (handlers == null)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
else
|
||||
{
|
||||
exceptions = new List<Exception>();
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (handlers != null)
|
||||
{
|
||||
// make snapshot
|
||||
var rentArray = ArrayPoolUtil.CopyToRentArray(handlers);
|
||||
var clearArray = true;
|
||||
try
|
||||
{
|
||||
var array = rentArray.Array;
|
||||
var len = rentArray.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
((IResolvePromise<T>)array[i]).TrySetResult(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (exceptions == null)
|
||||
{
|
||||
exceptions = new List<Exception>();
|
||||
}
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
array[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
rentArray.DisposeManually(clearArray);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions != null)
|
||||
{
|
||||
throw new AggregateException(exceptions);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySetCanceled(CancellationToken cancellationToken)
|
||||
{
|
||||
List<Exception> exceptions = null;
|
||||
|
||||
if (singleHandler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
((ICancelPromise)singleHandler).TrySetCanceled(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (handlers == null)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
else
|
||||
{
|
||||
exceptions = new List<Exception>();
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (handlers != null)
|
||||
{
|
||||
// make snapshot
|
||||
var rentArray = ArrayPoolUtil.CopyToRentArray(handlers);
|
||||
var clearArray = true;
|
||||
try
|
||||
{
|
||||
var array = rentArray.Array;
|
||||
var len = rentArray.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
((ICancelPromise)array[i]).TrySetCanceled(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (exceptions == null)
|
||||
{
|
||||
exceptions = new List<Exception>();
|
||||
}
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
array[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
rentArray.DisposeManually(clearArray);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions != null)
|
||||
{
|
||||
throw new AggregateException(exceptions);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Add(AsyncTriggerHandler<T> handler)
|
||||
{
|
||||
if (singleHandler == null)
|
||||
{
|
||||
singleHandler = handler;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (handlers != null)
|
||||
{
|
||||
handlers = new List<AsyncTriggerHandler<T>>();
|
||||
handlers.Add(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(AsyncTriggerHandler<T> handler)
|
||||
{
|
||||
if (singleHandler == handler)
|
||||
{
|
||||
singleHandler = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (handlers != null)
|
||||
{
|
||||
handlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user