#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)) #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member using System; using System.Collections.Generic; using System.Threading; using UniRx.Async.Internal; using UnityEngine; namespace UniRx.Async.Triggers { public interface ICancelablePromise { CancellationToken RegisteredCancellationToken { get; } bool TrySetCanceled(); } public class AsyncTriggerPromise : ReusablePromise, IPromise, ICancelablePromise { public CancellationToken RegisteredCancellationToken { get; private set; } public AsyncTriggerPromise() : this(CancellationToken.None) { } public AsyncTriggerPromise(CancellationToken cancellationToken) { this.RegisteredCancellationToken = cancellationToken; TaskTracker.TrackActiveTask(this); } public override T GetResult() { if (Status == AwaiterStatus.Pending) return RawResult; return base.GetResult(); } public override bool TrySetResult(T result) { if (Status == AwaiterStatus.Pending) { // keep status as Pending. this.ForceSetResult(result); TryInvokeContinuation(); return true; } return false; } public override bool TrySetCanceled() { if (Status == AwaiterStatus.Canceled) return false; TaskTracker.RemoveTracking(this); return base.TrySetCanceled(); } } public interface ICancellationTokenKeyDictionary { void Remove(CancellationToken token); } public class AsyncTriggerPromiseDictionary : Dictionary>, ICancellationTokenKeyDictionary, IEnumerable { public AsyncTriggerPromiseDictionary() : base(CancellationTokenEqualityComparer.Default) { } IEnumerator IEnumerable.GetEnumerator() { return Values.GetEnumerator(); } void ICancellationTokenKeyDictionary.Remove(CancellationToken token) { this.Remove(token); } } public abstract class AsyncTriggerBase : MonoBehaviour { static readonly Action Callback = CancelCallback; bool calledAwake = false; bool destroyCalled = false; CancellationTokenRegistration[] registeredCancellations; int registeredCancellationsCount; protected abstract IEnumerable GetPromises(); static protected IEnumerable Concat(ICancelablePromise p1, IEnumerable p1s) { if (p1 != null) yield return p1; if (p1s != null) foreach (var item in p1s) yield return item; } static protected IEnumerable Concat( ICancelablePromise p1, IEnumerable p1s, ICancelablePromise p2, IEnumerable 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 Concat( ICancelablePromise p1, IEnumerable p1s, ICancelablePromise p2, IEnumerable p2s, ICancelablePromise p3, IEnumerable 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 Concat( ICancelablePromise p1, IEnumerable p1s, ICancelablePromise p2, IEnumerable p2s, ICancelablePromise p3, IEnumerable p3s, ICancelablePromise p4, IEnumerable 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 Concat(params object[] promises) { foreach (var item in promises) { if (item is ICancelablePromise p) { yield return p; } else if (item is IEnumerable ps) { foreach (var p2 in ps) { yield return p2; } } } } protected UniTask GetOrAddPromise(ref AsyncTriggerPromise promise, ref AsyncTriggerPromiseDictionary promises, CancellationToken cancellationToken) { if (destroyCalled) return UniTask.FromCanceled(); if (!cancellationToken.CanBeCanceled) { if (promise == null) { promise = new AsyncTriggerPromise(); if (!calledAwake) { PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, new AwakeMonitor(this)); } } return promise.Task; } if (promises == null) promises = new AsyncTriggerPromiseDictionary(); if (promises.TryGetValue(cancellationToken, out var cancellablePromise)) { return cancellablePromise.Task; } cancellablePromise = new AsyncTriggerPromise(); 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.Shared.Rent(4); } ArrayPoolUtil.EnsureCapacity(ref registeredCancellations, registeredCancellationsCount + 1, ArrayPool.Shared); registeredCancellations[registeredCancellationsCount++] = registrationToken; return cancellablePromise.Task; } static void CancelCallback(object state) { var tuple = (Tuple)state; var dict = tuple.Item1; var promise = tuple.Item2; promise.TrySetCanceled(); dict.Remove(promise.RegisteredCancellationToken); } protected void TrySetResult(ReusablePromise promise, AsyncTriggerPromiseDictionary promises, T value) { if (promise != null) { promise.TrySetResult(value); } if (promises != null) { PromiseHelper.TrySetResultAll(promises.Values, value); } } void Awake() { calledAwake = true; } 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.Shared.Return(registeredCancellations); } } class AwakeMonitor : IPlayerLoopItem { readonly AsyncTriggerBase trigger; public AwakeMonitor(AsyncTriggerBase trigger) { this.trigger = trigger; } public bool MoveNext() { if (trigger.calledAwake) return false; if (trigger == null) { trigger.OnDestroy(); return false; } return true; } } } } #endif