From 49ca9364f7511e1ac6d587580ce6501f9b0a3981 Mon Sep 17 00:00:00 2001 From: neuecc Date: Fri, 12 Mar 2021 13:01:46 +0900 Subject: [PATCH] TimeoutController uses PlayerLoopTimer(WIP, needs test) --- .../CancellationTokenSourceExtensions.cs | 2 - .../UniTask/Runtime/PlayerLoopTimer.cs | 85 ++++--- .../UniTask/Runtime/TimeoutController.cs | 230 ++++-------------- 3 files changed, 108 insertions(+), 209 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/CancellationTokenSourceExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/CancellationTokenSourceExtensions.cs index ed15089..c519944 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/CancellationTokenSourceExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/CancellationTokenSourceExtensions.cs @@ -39,8 +39,6 @@ namespace Cysharp.Threading.Tasks var trigger = gameObject.GetAsyncDestroyTrigger(); trigger.CancellationToken.RegisterWithoutCaptureExecutionContext(CancelCancellationTokenSourceStateDelegate, cts); } - - } } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/PlayerLoopTimer.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/PlayerLoopTimer.cs index 03dc117..110c8cf 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/PlayerLoopTimer.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/PlayerLoopTimer.cs @@ -27,23 +27,31 @@ namespace Cysharp.Threading.Tasks this.state = state; } - public static PlayerLoopTimer Create(TimeSpan delayTimeSpan, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) + public static PlayerLoopTimer Create(TimeSpan interval, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) { +#if UNITY_EDITOR + // force use Realtime. + if (PlayerLoopHelper.IsMainThread && !UnityEditor.EditorApplication.isPlaying) + { + delayType = DelayType.Realtime; + } +#endif + switch (delayType) { case DelayType.UnscaledDeltaTime: - return new IgnoreTimeScalePlayerLoopTimer(delayTimeSpan, periodic, playerLoopTiming, cancellationToken, timerCallback, state); + return new IgnoreTimeScalePlayerLoopTimer(interval, periodic, playerLoopTiming, cancellationToken, timerCallback, state); case DelayType.Realtime: - return new RealtimePlayerLoopTimer(delayTimeSpan, periodic, playerLoopTiming, cancellationToken, timerCallback, state); + return new RealtimePlayerLoopTimer(interval, periodic, playerLoopTiming, cancellationToken, timerCallback, state); case DelayType.DeltaTime: default: - return new DeltaTimePlayerLoopTimer(delayTimeSpan, periodic, playerLoopTiming, cancellationToken, timerCallback, state); + return new DeltaTimePlayerLoopTimer(interval, periodic, playerLoopTiming, cancellationToken, timerCallback, state); } } - public static PlayerLoopTimer StartNew(TimeSpan delayTimeSpan, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) + public static PlayerLoopTimer StartNew(TimeSpan interval, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) { - var timer = Create(delayTimeSpan, periodic, delayType, playerLoopTiming, cancellationToken, timerCallback, state); + var timer = Create(interval, periodic, delayType, playerLoopTiming, cancellationToken, timerCallback, state); timer.Restart(); return timer; } @@ -55,7 +63,19 @@ namespace Cysharp.Threading.Tasks { if (isDisposed) throw new ObjectDisposedException(null); - ResetCore(); // init state + ResetCore(null); // init state + isPlaying = true; + PlayerLoopHelper.AddAction(playerLoopTiming, this); + } + + /// + /// Restart(Reset and Start) and change interval. + /// + public void Restart(TimeSpan interval) + { + if (isDisposed) throw new ObjectDisposedException(null); + + ResetCore(interval); // init state isPlaying = true; PlayerLoopHelper.AddAction(playerLoopTiming, this); } @@ -68,7 +88,7 @@ namespace Cysharp.Threading.Tasks isPlaying = false; } - protected abstract void ResetCore(); + protected abstract void ResetCore(TimeSpan? newInterval); public void Dispose() { @@ -87,7 +107,7 @@ namespace Cysharp.Threading.Tasks if (periodic) { - ResetCore(); + ResetCore(null); return true; } else @@ -106,13 +126,12 @@ namespace Cysharp.Threading.Tasks { int initialFrame; float elapsed; - readonly float delayTimeSpan; + float interval; - public DeltaTimePlayerLoopTimer(TimeSpan delayTimeSpan, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) + public DeltaTimePlayerLoopTimer(TimeSpan interval, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) : base(periodic, playerLoopTiming, cancellationToken, timerCallback, state) { - this.elapsed = 0.0f; - this.delayTimeSpan = (float)delayTimeSpan.TotalSeconds; + ResetCore(interval); this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; } @@ -127,7 +146,7 @@ namespace Cysharp.Threading.Tasks } elapsed += Time.deltaTime; - if (elapsed >= delayTimeSpan) + if (elapsed >= interval) { return false; } @@ -135,9 +154,13 @@ namespace Cysharp.Threading.Tasks return true; } - protected override void ResetCore() + protected override void ResetCore(TimeSpan? interval) { - elapsed = 0.0f; + this.elapsed = 0.0f; + if (interval != null) + { + this.interval = (float)interval.Value.TotalSeconds; + } } } @@ -145,13 +168,12 @@ namespace Cysharp.Threading.Tasks { int initialFrame; float elapsed; - readonly float delayTimeSpan; + float interval; - public IgnoreTimeScalePlayerLoopTimer(TimeSpan delayTimeSpan, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) + public IgnoreTimeScalePlayerLoopTimer(TimeSpan interval, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) : base(periodic, playerLoopTiming, cancellationToken, timerCallback, state) { - this.elapsed = 0.0f; - this.delayTimeSpan = (float)delayTimeSpan.TotalSeconds; + ResetCore(interval); this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; } @@ -166,7 +188,7 @@ namespace Cysharp.Threading.Tasks } elapsed += Time.unscaledDeltaTime; - if (elapsed >= delayTimeSpan) + if (elapsed >= interval) { return false; } @@ -174,27 +196,30 @@ namespace Cysharp.Threading.Tasks return true; } - protected override void ResetCore() + protected override void ResetCore(TimeSpan? interval) { elapsed = 0.0f; + if (interval != null) + { + this.interval = (float)interval.Value.TotalSeconds; + } } } sealed class RealtimePlayerLoopTimer : PlayerLoopTimer { ValueStopwatch stopwatch; - readonly long delayTimeSpanTicks; + long intervalTicks; - public RealtimePlayerLoopTimer(TimeSpan delayTimeSpan, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) + public RealtimePlayerLoopTimer(TimeSpan interval, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) : base(periodic, playerLoopTiming, cancellationToken, timerCallback, state) { - this.stopwatch = ValueStopwatch.StartNew(); - this.delayTimeSpanTicks = delayTimeSpan.Ticks; + ResetCore(interval); } protected override bool MoveNextCore() { - if (stopwatch.ElapsedTicks >= delayTimeSpanTicks) + if (stopwatch.ElapsedTicks >= intervalTicks) { return false; } @@ -202,9 +227,13 @@ namespace Cysharp.Threading.Tasks return true; } - protected override void ResetCore() + protected override void ResetCore(TimeSpan? interval) { this.stopwatch = ValueStopwatch.StartNew(); + if (interval != null) + { + this.intervalTicks = interval.Value.Ticks; + } } } } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/TimeoutController.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/TimeoutController.cs index 6dc5517..e38d7b7 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/TimeoutController.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/TimeoutController.cs @@ -1,8 +1,7 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -using System.Threading; using System; -using Cysharp.Threading.Tasks.Internal; +using System.Threading; namespace Cysharp.Threading.Tasks { @@ -14,26 +13,44 @@ namespace Cysharp.Threading.Tasks public sealed class TimeoutController : IDisposable { + readonly static Action CancelCancellationTokenSourceStateDelegate = new Action(CancelCancellationTokenSourceState); + + static void CancelCancellationTokenSourceState(object state) + { + var cts = (CancellationTokenSource)state; + cts.Cancel(); + } + CancellationTokenSource timeoutSource; CancellationTokenSource linkedSource; - StoppableDelayRealtimePromise timeoutDelay; + PlayerLoopTimer timer; + bool isDisposed; + readonly DelayType delayType; + readonly PlayerLoopTiming delayTiming; readonly CancellationTokenSource originalLinkCancellationTokenSource; - public TimeoutController() + public TimeoutController(DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update) { this.timeoutSource = new CancellationTokenSource(); this.originalLinkCancellationTokenSource = null; this.linkedSource = null; - this.timeoutDelay = null; + this.delayType = delayType; + this.delayTiming = delayTiming; } - public TimeoutController(CancellationTokenSource linkCancellationTokenSource) + public TimeoutController(CancellationTokenSource linkCancellationTokenSource, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update) { this.timeoutSource = new CancellationTokenSource(); this.originalLinkCancellationTokenSource = linkCancellationTokenSource; this.linkedSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, linkCancellationTokenSource.Token); - this.timeoutDelay = null; + this.delayType = delayType; + this.delayTiming = delayTiming; + } + + public CancellationToken Timeout(int millisecondsTimeout) + { + return Timeout(TimeSpan.FromMilliseconds(millisecondsTimeout)); } public CancellationToken Timeout(TimeSpan timeout) @@ -43,6 +60,7 @@ namespace Cysharp.Threading.Tasks return originalLinkCancellationTokenSource.Token; } + // Timeouted, create new source and timer. if (timeoutSource.IsCancellationRequested) { timeoutSource.Dispose(); @@ -53,18 +71,26 @@ namespace Cysharp.Threading.Tasks this.linkedSource.Dispose(); this.linkedSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, originalLinkCancellationTokenSource.Token); } + + timer?.Dispose(); + timer = null; } - if (timeoutDelay == null) + + var useSource = (linkedSource != null) ? linkedSource : timeoutSource; + var token = useSource.Token; + if (timer == null) { - RunDelayAsync(timeout).Forget(); // timeoutDelay = ... in RunDelayAsync(immediately, before await) + // Timer complete => timeoutSource.Cancel() -> linkedSource will be canceled. + // (linked)token is canceled => stop timer + timer = PlayerLoopTimer.Create(timeout, false, delayType, delayTiming, token, CancelCancellationTokenSourceStateDelegate, timeoutSource); } else { - timeoutDelay.RestartStopwatch(); // already running RunDelayAsync + timer.Restart(timeout); } - return (linkedSource != null) ? linkedSource.Token : timeoutSource.Token; + return token; } public bool IsTimeout() @@ -74,184 +100,30 @@ namespace Cysharp.Threading.Tasks public void Reset() { - if (timeoutDelay != null) - { - timeoutDelay.Stop(); // stop delay, will finish RunDelayAsync - timeoutDelay = null; - } - } - - async UniTaskVoid RunDelayAsync(TimeSpan timeout) - { - timeoutDelay = StoppableDelayRealtimePromise.Create(timeout, PlayerLoopTiming.Update, (linkedSource == null) ? CancellationToken.None : linkedSource.Token, out var version); - try - { - var reason = await new UniTask(timeoutDelay, version); - if (reason == DelayResult.DelayCompleted) - { - // UnityEngine.Debug.Log("DEBUG:Timeout Complete, try to call timeoutSource.Cancel"); - timeoutSource.Cancel(); - } - else if (reason == DelayResult.LinkedTokenCanceled) - { - // UnityEngine.Debug.Log("DEBUG:LinkedSource IsCancellationRequested"); - } - else if (reason == DelayResult.ExternalStopped) - { - // Reset(Promise.Stop) called, do nothing. - // UnityEngine.Debug.Log("DEBUG:Reset called"); - } - } - finally - { - timeoutDelay = null; - } + timer.Stop(); } public void Dispose() { - if (timeoutDelay != null) + if (isDisposed) return; + + try { - timeoutDelay.Stop(); - } - timeoutSource.Dispose(); - if (linkedSource != null) - { - linkedSource.Dispose(); - } - } + // stop timer. + timer.Dispose(); - enum DelayResult - { - LinkedTokenCanceled, - ExternalStopped, - DelayCompleted, // as Timeout. - } - - // Stop + SuppressCancellationThrow. - sealed class StoppableDelayRealtimePromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode - { - static OperationCanceledException ExterenalStopException = new OperationCanceledException(); - - static TaskPool pool; - StoppableDelayRealtimePromise nextNode; - public ref StoppableDelayRealtimePromise NextNode => ref nextNode; - - static StoppableDelayRealtimePromise() - { - TaskPool.RegisterSizeGetter(typeof(StoppableDelayRealtimePromise), () => pool.Size); - } - - long delayTimeSpanTicks; - ValueStopwatch stopwatch; - CancellationToken cancellationToken; - bool externalStop; - - UniTaskCompletionSourceCore core; - - StoppableDelayRealtimePromise() - { - } - - public static StoppableDelayRealtimePromise Create(TimeSpan delayTimeSpan, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) - { - if (!pool.TryPop(out var result)) + // cancel and dispose. + timeoutSource.Cancel(); + timeoutSource.Dispose(); + if (linkedSource != null) { - result = new StoppableDelayRealtimePromise(); - } - - result.stopwatch = ValueStopwatch.StartNew(); - result.delayTimeSpanTicks = delayTimeSpan.Ticks; - result.cancellationToken = cancellationToken; - result.externalStop = false; - - TaskTracker.TrackActiveTask(result, 3); - - PlayerLoopHelper.AddAction(timing, result); - - token = result.core.Version; - return result; - } - - public void Stop() - { - externalStop = true; - } - - public void RestartStopwatch() - { - stopwatch = ValueStopwatch.StartNew(); - } - - public DelayResult GetResult(short token) - { - try - { - return core.GetResult(token); - } - finally - { - TryReturn(); + linkedSource.Cancel(); + linkedSource.Dispose(); } } - - void IUniTaskSource.GetResult(short token) + finally { - GetResult(token); - } - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - public bool MoveNext() - { - if (cancellationToken.IsCancellationRequested) - { - core.TrySetResult(DelayResult.LinkedTokenCanceled); - return false; - } - - if (externalStop) - { - core.TrySetResult(DelayResult.ExternalStopped); - return false; - } - - if (stopwatch.IsInvalid) - { - core.TrySetResult(DelayResult.DelayCompleted); - return false; - } - - if (stopwatch.ElapsedTicks >= delayTimeSpanTicks) - { - core.TrySetResult(DelayResult.DelayCompleted); - return false; - } - - return true; - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - stopwatch = default; - cancellationToken = default; - externalStop = false; - return pool.TryPush(this); + isDisposed = true; } } }