#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Threading; using Cysharp.Threading.Tasks.Internal; namespace Cysharp.Threading.Tasks { public interface IResolvePromise { bool TrySetResult(); } public interface IResolvePromise { bool TrySetResult(T value); } public interface IRejectPromise { bool TrySetException(Exception exception); } public interface ICancelPromise { bool TrySetCanceled(CancellationToken cancellationToken = default); } public interface IPromise : IResolvePromise, IRejectPromise, ICancelPromise { } public interface IPromise : IResolvePromise, IRejectPromise, ICancelPromise { } internal class ExceptionHolder { ExceptionDispatchInfo exception; bool calledGet = false; public ExceptionHolder(ExceptionDispatchInfo exception) { this.exception = exception; } public ExceptionDispatchInfo GetException() { if (!calledGet) { calledGet = true; GC.SuppressFinalize(this); } return exception; } ~ExceptionHolder() { if (!calledGet) { UniTaskScheduler.PublishUnobservedTaskException(exception.SourceException); } } } [StructLayout(LayoutKind.Auto)] public struct UniTaskCompletionSourceCore { // Struct Size: TResult + (8 + 2 + 1 + 1 + 8 + 8) TResult result; object error; // ExceptionHolder or OperationCanceledException short version; bool hasUnhandledError; int completedCount; // 0: completed == false Action continuation; object continuationState; [DebuggerHidden] public void Reset() { ReportUnhandledError(); unchecked { version += 1; // incr version. } completedCount = 0; result = default; error = null; hasUnhandledError = false; continuation = null; continuationState = null; } void ReportUnhandledError() { if (hasUnhandledError) { try { if (error is OperationCanceledException oc) { UniTaskScheduler.PublishUnobservedTaskException(oc); } else if (error is ExceptionHolder e) { UniTaskScheduler.PublishUnobservedTaskException(e.GetException().SourceException); } } catch { } } } internal void MarkHandled() { hasUnhandledError = false; } /// Completes with a successful result. /// The result. [DebuggerHidden] public bool TrySetResult(TResult result) { if (Interlocked.Increment(ref completedCount) == 1) { // setup result this.result = result; if (continuation != null || Interlocked.CompareExchange(ref this.continuation, UniTaskCompletionSourceCoreShared.s_sentinel, null) != null) { continuation(continuationState); return true; } } return false; } /// Completes with an error. /// The exception. [DebuggerHidden] public bool TrySetException(Exception error) { if (Interlocked.Increment(ref completedCount) == 1) { // setup result this.hasUnhandledError = true; if (error is OperationCanceledException) { this.error = error; } else { this.error = new ExceptionHolder(ExceptionDispatchInfo.Capture(error)); } if (continuation != null || Interlocked.CompareExchange(ref this.continuation, UniTaskCompletionSourceCoreShared.s_sentinel, null) != null) { continuation(continuationState); return true; } } return false; } [DebuggerHidden] public bool TrySetCanceled(CancellationToken cancellationToken = default) { if (Interlocked.Increment(ref completedCount) == 1) { // setup result this.hasUnhandledError = true; this.error = new OperationCanceledException(cancellationToken); if (continuation != null || Interlocked.CompareExchange(ref this.continuation, UniTaskCompletionSourceCoreShared.s_sentinel, null) != null) { continuation(continuationState); return true; } } return false; } /// Gets the operation version. [DebuggerHidden] public short Version => version; /// Gets the status of the operation. /// Opaque value that was provided to the 's constructor. [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] public UniTaskStatus GetStatus(short token) { ValidateToken(token); return (continuation == null || (completedCount == 0)) ? UniTaskStatus.Pending : (error == null) ? UniTaskStatus.Succeeded : (error is OperationCanceledException) ? UniTaskStatus.Canceled : UniTaskStatus.Faulted; } /// Gets the status of the operation without token validation. [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] public UniTaskStatus UnsafeGetStatus() { return (continuation == null || (completedCount == 0)) ? UniTaskStatus.Pending : (error == null) ? UniTaskStatus.Succeeded : (error is OperationCanceledException) ? UniTaskStatus.Canceled : UniTaskStatus.Faulted; } /// Gets the result of the operation. /// Opaque value that was provided to the 's constructor. // [StackTraceHidden] [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] public TResult GetResult(short token) { ValidateToken(token); if (completedCount == 0) { throw new InvalidOperationException("Not yet completed, UniTask only allow to use await."); } if (error != null) { hasUnhandledError = false; if (error is OperationCanceledException oce) { throw oce; } else if (error is ExceptionHolder eh) { eh.GetException().Throw(); } throw new InvalidOperationException("Critical: invalid exception type was held."); } return result; } /// Schedules the continuation action for this operation. /// The continuation to invoke when the operation has completed. /// The state object to pass to when it's invoked. /// Opaque value that was provided to the 's constructor. [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void OnCompleted(Action continuation, object state, short token /*, ValueTaskSourceOnCompletedFlags flags */) { if (continuation == null) { throw new ArgumentNullException(nameof(continuation)); } ValidateToken(token); /* no use ValueTaskSourceOnCOmpletedFlags, always no capture ExecutionContext and SynchronizationContext. */ /* PatternA: GetStatus=Pending => OnCompleted => TrySet*** => GetResult PatternB: TrySet*** => GetStatus=!Pending => GetResult PatternC: GetStatus=Pending => TrySet/OnCompleted(race condition) => GetResult C.1: win OnCompleted -> TrySet invoke saved continuation C.2: win TrySet -> should invoke continuation here. */ // not set continuation yet. object oldContinuation = this.continuation; if (oldContinuation == null) { continuationState = state; oldContinuation = Interlocked.CompareExchange(ref this.continuation, continuation, null); } if (oldContinuation != null) { // already running continuation in TrySet. // It will cause call OnCompleted multiple time, invalid. if (!ReferenceEquals(oldContinuation, UniTaskCompletionSourceCoreShared.s_sentinel)) { throw new InvalidOperationException("Already continuation registered, can not await twice or get Status after await."); } continuation(state); } } [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ValidateToken(short token) { if (token != version) { throw new InvalidOperationException("Token version is not matched, can not await twice or get Status after await."); } } } internal static class UniTaskCompletionSourceCoreShared // separated out of generic to avoid unnecessary duplication { internal static readonly Action s_sentinel = CompletionSentinel; private static void CompletionSentinel(object _) // named method to aid debugging { throw new InvalidOperationException("The sentinel delegate should never be invoked."); } } public class AutoResetUniTaskCompletionSource : IUniTaskSource, ITaskPoolNode, IPromise { static TaskPool pool; AutoResetUniTaskCompletionSource nextNode; public ref AutoResetUniTaskCompletionSource NextNode => ref nextNode; static AutoResetUniTaskCompletionSource() { TaskPool.RegisterSizeGetter(typeof(AutoResetUniTaskCompletionSource), () => pool.Size); } UniTaskCompletionSourceCore core; AutoResetUniTaskCompletionSource() { } [DebuggerHidden] public static AutoResetUniTaskCompletionSource Create() { if (!pool.TryPop(out var result)) { result = new AutoResetUniTaskCompletionSource(); } TaskTracker.TrackActiveTask(result, 2); return result; } [DebuggerHidden] public static AutoResetUniTaskCompletionSource CreateFromCanceled(CancellationToken cancellationToken, out short token) { var source = Create(); source.TrySetCanceled(cancellationToken); token = source.core.Version; return source; } [DebuggerHidden] public static AutoResetUniTaskCompletionSource CreateFromException(Exception exception, out short token) { var source = Create(); source.TrySetException(exception); token = source.core.Version; return source; } [DebuggerHidden] public static AutoResetUniTaskCompletionSource CreateCompleted(out short token) { var source = Create(); source.TrySetResult(); token = source.core.Version; return source; } public UniTask Task { [DebuggerHidden] get { return new UniTask(this, core.Version); } } [DebuggerHidden] public bool TrySetResult() { return core.TrySetResult(AsyncUnit.Default); } [DebuggerHidden] public bool TrySetCanceled(CancellationToken cancellationToken = default) { return core.TrySetCanceled(cancellationToken); } [DebuggerHidden] public bool TrySetException(Exception exception) { return core.TrySetException(exception); } [DebuggerHidden] public void GetResult(short token) { try { core.GetResult(token); } finally { TryReturn(); } } [DebuggerHidden] public UniTaskStatus GetStatus(short token) { return core.GetStatus(token); } [DebuggerHidden] public UniTaskStatus UnsafeGetStatus() { return core.UnsafeGetStatus(); } [DebuggerHidden] public void OnCompleted(Action continuation, object state, short token) { core.OnCompleted(continuation, state, token); } [DebuggerHidden] bool TryReturn() { TaskTracker.RemoveTracking(this); core.Reset(); return pool.TryPush(this); } } public class AutoResetUniTaskCompletionSource : IUniTaskSource, ITaskPoolNode>, IPromise { static TaskPool> pool; AutoResetUniTaskCompletionSource nextNode; public ref AutoResetUniTaskCompletionSource NextNode => ref nextNode; static AutoResetUniTaskCompletionSource() { TaskPool.RegisterSizeGetter(typeof(AutoResetUniTaskCompletionSource), () => pool.Size); } UniTaskCompletionSourceCore core; AutoResetUniTaskCompletionSource() { } [DebuggerHidden] public static AutoResetUniTaskCompletionSource Create() { if (!pool.TryPop(out var result)) { result = new AutoResetUniTaskCompletionSource(); } TaskTracker.TrackActiveTask(result, 2); return result; } [DebuggerHidden] public static AutoResetUniTaskCompletionSource CreateFromCanceled(CancellationToken cancellationToken, out short token) { var source = Create(); source.TrySetCanceled(cancellationToken); token = source.core.Version; return source; } [DebuggerHidden] public static AutoResetUniTaskCompletionSource CreateFromException(Exception exception, out short token) { var source = Create(); source.TrySetException(exception); token = source.core.Version; return source; } [DebuggerHidden] public static AutoResetUniTaskCompletionSource CreateFromResult(T result, out short token) { var source = Create(); source.TrySetResult(result); token = source.core.Version; return source; } public UniTask Task { [DebuggerHidden] get { return new UniTask(this, core.Version); } } [DebuggerHidden] public bool TrySetResult(T result) { return core.TrySetResult(result); } [DebuggerHidden] public bool TrySetCanceled(CancellationToken cancellationToken = default) { return core.TrySetCanceled(cancellationToken); } [DebuggerHidden] public bool TrySetException(Exception exception) { return core.TrySetException(exception); } [DebuggerHidden] public T GetResult(short token) { try { return core.GetResult(token); } finally { TryReturn(); } } [DebuggerHidden] void IUniTaskSource.GetResult(short token) { GetResult(token); } [DebuggerHidden] public UniTaskStatus GetStatus(short token) { return core.GetStatus(token); } [DebuggerHidden] public UniTaskStatus UnsafeGetStatus() { return core.UnsafeGetStatus(); } [DebuggerHidden] public void OnCompleted(Action continuation, object state, short token) { core.OnCompleted(continuation, state, token); } [DebuggerHidden] bool TryReturn() { TaskTracker.RemoveTracking(this); core.Reset(); return pool.TryPush(this); } } public class UniTaskCompletionSource : IUniTaskSource, IPromise { CancellationToken cancellationToken; ExceptionHolder exception; object gate; Action singleContinuation; object singleState; List<(Action, object)> secondaryContinuationList; int intStatus; // UniTaskStatus bool handled = false; public UniTaskCompletionSource() { TaskTracker.TrackActiveTask(this, 2); } [DebuggerHidden] internal void MarkHandled() { if (!handled) { handled = true; TaskTracker.RemoveTracking(this); } } public UniTask Task { [DebuggerHidden] get { return new UniTask(this, 0); } } [DebuggerHidden] public bool TrySetResult() { return TrySignalCompletion(UniTaskStatus.Succeeded); } [DebuggerHidden] public bool TrySetCanceled(CancellationToken cancellationToken = default) { if (UnsafeGetStatus() != UniTaskStatus.Pending) return false; this.cancellationToken = cancellationToken; return TrySignalCompletion(UniTaskStatus.Canceled); } [DebuggerHidden] public bool TrySetException(Exception exception) { if (exception is OperationCanceledException oce) { return TrySetCanceled(oce.CancellationToken); } if (UnsafeGetStatus() != UniTaskStatus.Pending) return false; this.exception = new ExceptionHolder(ExceptionDispatchInfo.Capture(exception)); return TrySignalCompletion(UniTaskStatus.Faulted); } [DebuggerHidden] public void GetResult(short token) { MarkHandled(); var status = (UniTaskStatus)intStatus; switch (status) { case UniTaskStatus.Succeeded: return; case UniTaskStatus.Faulted: exception.GetException().Throw(); return; case UniTaskStatus.Canceled: throw new OperationCanceledException(cancellationToken); default: case UniTaskStatus.Pending: throw new InvalidOperationException("not yet completed."); } } [DebuggerHidden] public UniTaskStatus GetStatus(short token) { return (UniTaskStatus)intStatus; } [DebuggerHidden] public UniTaskStatus UnsafeGetStatus() { return (UniTaskStatus)intStatus; } [DebuggerHidden] public void OnCompleted(Action continuation, object state, short token) { if (gate == null) { Interlocked.CompareExchange(ref gate, new object(), null); } var lockGate = Thread.VolatileRead(ref gate); lock (lockGate) // wait TrySignalCompletion, after status is not pending. { if ((UniTaskStatus)intStatus != UniTaskStatus.Pending) { continuation(state); return; } if (singleContinuation == null) { singleContinuation = continuation; singleState = state; } else { if (secondaryContinuationList == null) { secondaryContinuationList = new List<(Action, object)>(); } secondaryContinuationList.Add((continuation, state)); } } } [DebuggerHidden] bool TrySignalCompletion(UniTaskStatus status) { if (Interlocked.CompareExchange(ref intStatus, (int)status, (int)UniTaskStatus.Pending) == (int)UniTaskStatus.Pending) { if (gate == null) { Interlocked.CompareExchange(ref gate, new object(), null); } var lockGate = Thread.VolatileRead(ref gate); lock (lockGate) // wait OnCompleted. { if (singleContinuation != null) { try { singleContinuation(singleState); } catch (Exception ex) { UniTaskScheduler.PublishUnobservedTaskException(ex); } } if (secondaryContinuationList != null) { foreach (var (c, state) in secondaryContinuationList) { try { c(state); } catch (Exception ex) { UniTaskScheduler.PublishUnobservedTaskException(ex); } } } singleContinuation = null; singleState = null; secondaryContinuationList = null; } return true; } return false; } } public class UniTaskCompletionSource : IUniTaskSource, IPromise { CancellationToken cancellationToken; T result; ExceptionHolder exception; object gate; Action singleContinuation; object singleState; List<(Action, object)> secondaryContinuationList; int intStatus; // UniTaskStatus bool handled = false; public UniTaskCompletionSource() { TaskTracker.TrackActiveTask(this, 2); } [DebuggerHidden] internal void MarkHandled() { if (!handled) { handled = true; TaskTracker.RemoveTracking(this); } } public UniTask Task { [DebuggerHidden] get { return new UniTask(this, 0); } } [DebuggerHidden] public bool TrySetResult(T result) { if (UnsafeGetStatus() != UniTaskStatus.Pending) return false; this.result = result; return TrySignalCompletion(UniTaskStatus.Succeeded); } [DebuggerHidden] public bool TrySetCanceled(CancellationToken cancellationToken = default) { if (UnsafeGetStatus() != UniTaskStatus.Pending) return false; this.cancellationToken = cancellationToken; return TrySignalCompletion(UniTaskStatus.Canceled); } [DebuggerHidden] public bool TrySetException(Exception exception) { if (exception is OperationCanceledException oce) { return TrySetCanceled(oce.CancellationToken); } if (UnsafeGetStatus() != UniTaskStatus.Pending) return false; this.exception = new ExceptionHolder(ExceptionDispatchInfo.Capture(exception)); return TrySignalCompletion(UniTaskStatus.Faulted); } [DebuggerHidden] public T GetResult(short token) { MarkHandled(); var status = (UniTaskStatus)intStatus; switch (status) { case UniTaskStatus.Succeeded: return result; case UniTaskStatus.Faulted: exception.GetException().Throw(); return default; case UniTaskStatus.Canceled: throw new OperationCanceledException(cancellationToken); default: case UniTaskStatus.Pending: throw new InvalidOperationException("not yet completed."); } } [DebuggerHidden] void IUniTaskSource.GetResult(short token) { GetResult(token); } [DebuggerHidden] public UniTaskStatus GetStatus(short token) { return (UniTaskStatus)intStatus; } [DebuggerHidden] public UniTaskStatus UnsafeGetStatus() { return (UniTaskStatus)intStatus; } [DebuggerHidden] public void OnCompleted(Action continuation, object state, short token) { if (gate == null) { Interlocked.CompareExchange(ref gate, new object(), null); } var lockGate = Thread.VolatileRead(ref gate); lock (lockGate) // wait TrySignalCompletion, after status is not pending. { if ((UniTaskStatus)intStatus != UniTaskStatus.Pending) { continuation(state); return; } if (singleContinuation == null) { singleContinuation = continuation; singleState = state; } else { if (secondaryContinuationList == null) { secondaryContinuationList = new List<(Action, object)>(); } secondaryContinuationList.Add((continuation, state)); } } } [DebuggerHidden] bool TrySignalCompletion(UniTaskStatus status) { if (Interlocked.CompareExchange(ref intStatus, (int)status, (int)UniTaskStatus.Pending) == (int)UniTaskStatus.Pending) { if (gate == null) { Interlocked.CompareExchange(ref gate, new object(), null); } var lockGate = Thread.VolatileRead(ref gate); lock (lockGate) // wait OnCompleted. { if (singleContinuation != null) { try { singleContinuation(singleState); } catch (Exception ex) { UniTaskScheduler.PublishUnobservedTaskException(ex); } } if (secondaryContinuationList != null) { foreach (var (c, state) in secondaryContinuationList) { try { c(state); } catch (Exception ex) { UniTaskScheduler.PublishUnobservedTaskException(ex); } } } singleContinuation = null; singleState = null; secondaryContinuationList = null; } return true; } return false; } } }