diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/CancellationTokenSourceExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/CancellationTokenSourceExtensions.cs index 70fb10b..ed15089 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/CancellationTokenSourceExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/CancellationTokenSourceExtensions.cs @@ -9,28 +9,24 @@ using Cysharp.Threading.Tasks.Internal; namespace Cysharp.Threading.Tasks { - public static class CancellationTokenSourceExtensions + public static partial class CancellationTokenSourceExtensions { - public static void CancelAfterSlim(this CancellationTokenSource cts, int millisecondsDelay, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update) + readonly static Action CancelCancellationTokenSourceStateDelegate = new Action(CancelCancellationTokenSourceState); + + static void CancelCancellationTokenSourceState(object state) { - var delay = UniTask.Delay(millisecondsDelay, delayType, delayTiming, cts.Token); - CancelAfterCore(cts, delay).Forget(); + var cts = (CancellationTokenSource)state; + cts.Cancel(); } - public static void CancelAfterSlim(this CancellationTokenSource cts, TimeSpan delayTimeSpan, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update) + public static IDisposable CancelAfterSlim(this CancellationTokenSource cts, int millisecondsDelay, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update) { - var delay = UniTask.Delay(delayTimeSpan, delayType, delayTiming, cts.Token); - CancelAfterCore(cts, delay).Forget(); + return CancelAfterSlim(cts, TimeSpan.FromMilliseconds(millisecondsDelay), delayType, delayTiming); } - static async UniTaskVoid CancelAfterCore(CancellationTokenSource cts, UniTask delayTask) + public static IDisposable CancelAfterSlim(this CancellationTokenSource cts, TimeSpan delayTimeSpan, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update) { - var alreadyCanceled = await delayTask.SuppressCancellationThrow(); - if (!alreadyCanceled) - { - cts.Cancel(); - cts.Dispose(); - } + return PlayerLoopTimer.StartNew(delayTimeSpan, false, delayType, delayTiming, cts.Token, CancelCancellationTokenSourceStateDelegate, cts); } public static void RegisterRaiseCancelOnDestroy(this CancellationTokenSource cts, Component component) @@ -41,209 +37,10 @@ namespace Cysharp.Threading.Tasks public static void RegisterRaiseCancelOnDestroy(this CancellationTokenSource cts, GameObject gameObject) { var trigger = gameObject.GetAsyncDestroyTrigger(); - trigger.CancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var cts2 = (CancellationTokenSource)state; - cts2.Cancel(); - }, cts); + trigger.CancellationToken.RegisterWithoutCaptureExecutionContext(CancelCancellationTokenSourceStateDelegate, cts); } - // TODO:delete this. - public static IDisposable CancelAfterSlim2(this CancellationTokenSource cts, TimeSpan delayTimeSpan) - { - var timer = PlayerLoopTimer.Create(delayTimeSpan, DelayType.DeltaTime, PlayerLoopTiming.Update, cts.Token, state => - { - var x = (CancellationTokenSource)state; - x.Cancel(); - }, cts); - timer.Restart(); - - return timer; - } - - abstract class PlayerLoopTimer : IDisposable, IPlayerLoopItem - { - readonly CancellationToken cancellationToken; - readonly Action timerCallback; - readonly object state; - readonly PlayerLoopTiming playerLoopTiming; - - bool isPlaying; - bool isDisposed; - - protected PlayerLoopTimer(PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) - { - this.playerLoopTiming = playerLoopTiming; - this.cancellationToken = cancellationToken; - this.timerCallback = timerCallback; - this.state = state; - } - - public static PlayerLoopTimer Create(TimeSpan delayTimeSpan, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) - { - switch (delayType) - { - case DelayType.UnscaledDeltaTime: - return new IgnoreTimeScalePlayerLoopTimer(delayTimeSpan, playerLoopTiming, cancellationToken, timerCallback, state); - case DelayType.Realtime: - return new RealtimePlayerLoopTimer(delayTimeSpan, playerLoopTiming, cancellationToken, timerCallback, state); - case DelayType.DeltaTime: - default: - return new DeltaTimePlayerLoopTimer(delayTimeSpan, playerLoopTiming, cancellationToken, timerCallback, state); - } - } - - /// - /// Restart(Reset and Start) timer. - /// - public void Restart() - { - if (isDisposed) throw new ObjectDisposedException(null); - if (isPlaying) return; - - ResetCore(); // init state - isPlaying = true; - PlayerLoopHelper.AddAction(playerLoopTiming, this); - } - - /// - /// Stop timer. - /// - public void Stop() - { - isPlaying = false; - } - - protected abstract void ResetCore(); - - public void Dispose() - { - isDisposed = true; - } - - bool IPlayerLoopItem.MoveNext() - { - if (isDisposed) return false; - if (!isPlaying) return false; - if (cancellationToken.IsCancellationRequested) return false; - - if (!MoveNextCore()) - { - timerCallback(state); - return false; - } - - return true; - } - - protected abstract bool MoveNextCore(); - } - - sealed class DeltaTimePlayerLoopTimer : PlayerLoopTimer - { - int initialFrame; - float elapsed; - readonly float delayTimeSpan; - - public DeltaTimePlayerLoopTimer(TimeSpan delayTimeSpan, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) - : base(playerLoopTiming, cancellationToken, timerCallback, state) - { - this.elapsed = 0.0f; - this.delayTimeSpan = (float)delayTimeSpan.TotalSeconds; - this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; - } - - protected override bool MoveNextCore() - { - if (elapsed == 0.0f) - { - if (initialFrame == Time.frameCount) - { - return true; - } - } - - elapsed += Time.deltaTime; - if (elapsed >= delayTimeSpan) - { - return false; - } - - return true; - } - - protected override void ResetCore() - { - elapsed = 0.0f; - } - } - - sealed class IgnoreTimeScalePlayerLoopTimer : PlayerLoopTimer - { - int initialFrame; - float elapsed; - readonly float delayTimeSpan; - - public IgnoreTimeScalePlayerLoopTimer(TimeSpan delayTimeSpan, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) - : base(playerLoopTiming, cancellationToken, timerCallback, state) - { - this.elapsed = 0.0f; - this.delayTimeSpan = (float)delayTimeSpan.TotalSeconds; - this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; - } - - protected override bool MoveNextCore() - { - if (elapsed == 0.0f) - { - if (initialFrame == Time.frameCount) - { - return true; - } - } - - elapsed += Time.unscaledDeltaTime; - if (elapsed >= delayTimeSpan) - { - return false; - } - - return true; - } - - protected override void ResetCore() - { - elapsed = 0.0f; - } - } - - sealed class RealtimePlayerLoopTimer : PlayerLoopTimer - { - ValueStopwatch stopwatch; - readonly long delayTimeSpanTicks; - - public RealtimePlayerLoopTimer(TimeSpan delayTimeSpan, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) - : base(playerLoopTiming, cancellationToken, timerCallback, state) - { - this.stopwatch = ValueStopwatch.StartNew(); - this.delayTimeSpanTicks = delayTimeSpan.Ticks; - } - - protected override bool MoveNextCore() - { - if (stopwatch.ElapsedTicks >= delayTimeSpanTicks) - { - return false; - } - - return true; - } - - protected override void ResetCore() - { - this.stopwatch = ValueStopwatch.StartNew(); - } - } + } } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/PlayerLoopTimer.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/PlayerLoopTimer.cs new file mode 100644 index 0000000..03dc117 --- /dev/null +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/PlayerLoopTimer.cs @@ -0,0 +1,211 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +using System.Threading; +using System; +using Cysharp.Threading.Tasks.Internal; +using UnityEngine; + +namespace Cysharp.Threading.Tasks +{ + public abstract class PlayerLoopTimer : IDisposable, IPlayerLoopItem + { + readonly CancellationToken cancellationToken; + readonly Action timerCallback; + readonly object state; + readonly PlayerLoopTiming playerLoopTiming; + readonly bool periodic; + + bool isPlaying; + bool isDisposed; + + protected PlayerLoopTimer(bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) + { + this.periodic = periodic; + this.playerLoopTiming = playerLoopTiming; + this.cancellationToken = cancellationToken; + this.timerCallback = timerCallback; + this.state = state; + } + + public static PlayerLoopTimer Create(TimeSpan delayTimeSpan, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) + { + switch (delayType) + { + case DelayType.UnscaledDeltaTime: + return new IgnoreTimeScalePlayerLoopTimer(delayTimeSpan, periodic, playerLoopTiming, cancellationToken, timerCallback, state); + case DelayType.Realtime: + return new RealtimePlayerLoopTimer(delayTimeSpan, periodic, playerLoopTiming, cancellationToken, timerCallback, state); + case DelayType.DeltaTime: + default: + return new DeltaTimePlayerLoopTimer(delayTimeSpan, periodic, playerLoopTiming, cancellationToken, timerCallback, state); + } + } + + public static PlayerLoopTimer StartNew(TimeSpan delayTimeSpan, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) + { + var timer = Create(delayTimeSpan, periodic, delayType, playerLoopTiming, cancellationToken, timerCallback, state); + timer.Restart(); + return timer; + } + + /// + /// Restart(Reset and Start) timer. + /// + public void Restart() + { + if (isDisposed) throw new ObjectDisposedException(null); + + ResetCore(); // init state + isPlaying = true; + PlayerLoopHelper.AddAction(playerLoopTiming, this); + } + + /// + /// Stop timer. + /// + public void Stop() + { + isPlaying = false; + } + + protected abstract void ResetCore(); + + public void Dispose() + { + isDisposed = true; + } + + bool IPlayerLoopItem.MoveNext() + { + if (isDisposed) return false; + if (!isPlaying) return false; + if (cancellationToken.IsCancellationRequested) return false; + + if (!MoveNextCore()) + { + timerCallback(state); + + if (periodic) + { + ResetCore(); + return true; + } + else + { + return false; + } + } + + return true; + } + + protected abstract bool MoveNextCore(); + } + + sealed class DeltaTimePlayerLoopTimer : PlayerLoopTimer + { + int initialFrame; + float elapsed; + readonly float delayTimeSpan; + + public DeltaTimePlayerLoopTimer(TimeSpan delayTimeSpan, 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; + this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; + } + + protected override bool MoveNextCore() + { + if (elapsed == 0.0f) + { + if (initialFrame == Time.frameCount) + { + return true; + } + } + + elapsed += Time.deltaTime; + if (elapsed >= delayTimeSpan) + { + return false; + } + + return true; + } + + protected override void ResetCore() + { + elapsed = 0.0f; + } + } + + sealed class IgnoreTimeScalePlayerLoopTimer : PlayerLoopTimer + { + int initialFrame; + float elapsed; + readonly float delayTimeSpan; + + public IgnoreTimeScalePlayerLoopTimer(TimeSpan delayTimeSpan, 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; + this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; + } + + protected override bool MoveNextCore() + { + if (elapsed == 0.0f) + { + if (initialFrame == Time.frameCount) + { + return true; + } + } + + elapsed += Time.unscaledDeltaTime; + if (elapsed >= delayTimeSpan) + { + return false; + } + + return true; + } + + protected override void ResetCore() + { + elapsed = 0.0f; + } + } + + sealed class RealtimePlayerLoopTimer : PlayerLoopTimer + { + ValueStopwatch stopwatch; + readonly long delayTimeSpanTicks; + + public RealtimePlayerLoopTimer(TimeSpan delayTimeSpan, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) + : base(periodic, playerLoopTiming, cancellationToken, timerCallback, state) + { + this.stopwatch = ValueStopwatch.StartNew(); + this.delayTimeSpanTicks = delayTimeSpan.Ticks; + } + + protected override bool MoveNextCore() + { + if (stopwatch.ElapsedTicks >= delayTimeSpanTicks) + { + return false; + } + + return true; + } + + protected override void ResetCore() + { + this.stopwatch = ValueStopwatch.StartNew(); + } + } +} +