diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Internal/ValueStopwatch.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/Internal/ValueStopwatch.cs new file mode 100644 index 0000000..fa8a8ea --- /dev/null +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Internal/ValueStopwatch.cs @@ -0,0 +1,35 @@ +using System; +using System.Diagnostics; + +namespace Cysharp.Threading.Tasks.Internal +{ + internal readonly struct ValueStopwatch + { + static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; + + readonly long startTimestamp; + + public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp()); + + ValueStopwatch(long startTimestamp) + { + this.startTimestamp = startTimestamp; + } + + public TimeSpan Elapsed => TimeSpan.FromTicks(this.ElapsedTicks); + + public long ElapsedTicks + { + get + { + if (startTimestamp == 0) + { + throw new InvalidOperationException("Detected invalid initialization(use 'default'), only to create from StartNew()."); + } + + var delta = Stopwatch.GetTimestamp() - startTimestamp; + return (long)(delta * TimestampToTicks); + } + } + } +} \ No newline at end of file diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Internal/ValueStopwatch.cs.meta b/src/UniTask/Assets/Plugins/UniTask/Runtime/Internal/ValueStopwatch.cs.meta new file mode 100644 index 0000000..b7c6b09 --- /dev/null +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Internal/ValueStopwatch.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f16fb466974ad034c8732c79c7fd67ea +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs index 1df46cb..a02d7d0 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs @@ -1,7 +1,7 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +using Cysharp.Threading.Tasks.Internal; using System; -using System.IO; using System.Runtime.CompilerServices; using System.Threading; using UnityEngine; @@ -96,6 +96,16 @@ namespace Cysharp.Threading.Tasks : new UniTask(DelayPromise.Create(delayTimeSpan, delayTiming, cancellationToken, out token), token); } + public static UniTask DelayRealtime(TimeSpan delayTimeSpan, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + { + if (delayTimeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException("Delay does not allow minus delayTimeSpan. delayTimeSpan:" + delayTimeSpan); + } + + return new UniTask(DelayRealtimePromise.Create(delayTimeSpan, delayTiming, cancellationToken, out var token), token); + } + sealed class YieldPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -404,7 +414,7 @@ namespace Cysharp.Threading.Tasks } int initialFrame; - float delayFrameTimeSpan; + float delayTimeSpan; float elapsed; CancellationToken cancellationToken; @@ -414,7 +424,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(TimeSpan delayFrameTimeSpan, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(TimeSpan delayTimeSpan, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -427,7 +437,7 @@ namespace Cysharp.Threading.Tasks } result.elapsed = 0.0f; - result.delayFrameTimeSpan = (float)delayFrameTimeSpan.TotalSeconds; + result.delayTimeSpan = (float)delayTimeSpan.TotalSeconds; result.cancellationToken = cancellationToken; result.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; @@ -483,7 +493,7 @@ namespace Cysharp.Threading.Tasks } elapsed += Time.deltaTime; - if (elapsed >= delayFrameTimeSpan) + if (elapsed >= delayTimeSpan) { core.TrySetResult(null); return false; @@ -496,7 +506,7 @@ namespace Cysharp.Threading.Tasks { TaskTracker.RemoveTracking(this); core.Reset(); - delayFrameTimeSpan = default; + delayTimeSpan = default; elapsed = default; cancellationToken = default; return pool.TryPush(this); @@ -612,6 +622,104 @@ namespace Cysharp.Threading.Tasks return pool.TryPush(this); } } + + sealed class DelayRealtimePromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + { + static TaskPool pool; + public DelayRealtimePromise NextNode { get; set; } + + static DelayRealtimePromise() + { + TaskPool.RegisterSizeGetter(typeof(DelayRealtimePromise), () => pool.Size); + } + + long delayTimeSpanTicks; + ValueStopwatch stopwatch; + CancellationToken cancellationToken; + + UniTaskCompletionSourceCore core; + + DelayRealtimePromise() + { + } + + public static IUniTaskSource Create(TimeSpan delayTimeSpan, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new DelayRealtimePromise(); + } + + result.stopwatch = ValueStopwatch.StartNew(); + result.delayTimeSpanTicks = delayTimeSpan.Ticks; + result.cancellationToken = cancellationToken; + + TaskTracker.TrackActiveTask(result, 3); + + PlayerLoopHelper.AddAction(timing, result); + + token = result.core.Version; + return result; + } + + public void GetResult(short token) + { + try + { + core.GetResult(token); + } + finally + { + TryReturn(); + } + } + + 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.TrySetCanceled(cancellationToken); + return false; + } + + if (stopwatch.ElapsedTicks >= delayTimeSpanTicks) + { + core.TrySetResult(AsyncUnit.Default); + return false; + } + + return true; + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + stopwatch = default; + cancellationToken = default; + return pool.TryPush(this); + } + } } public readonly struct YieldAwaitable diff --git a/src/UniTask/Assets/Tests/DelayTest.cs b/src/UniTask/Assets/Tests/DelayTest.cs index 0a2352a..25de6e2 100644 --- a/src/UniTask/Assets/Tests/DelayTest.cs +++ b/src/UniTask/Assets/Tests/DelayTest.cs @@ -16,164 +16,164 @@ namespace Cysharp.Threading.TasksTests { public class DelayTest { - //[UnityTest] - //public IEnumerator DelayFrame() => UniTask.ToCoroutine(async () => - //{ - // for (int i = 1; i < 5; i++) - // { - // await UniTask.Yield(PlayerLoopTiming.PreUpdate); - // var frameCount = Time.frameCount; - // await UniTask.DelayFrame(i); - // Time.frameCount.Should().Be(frameCount + i); - // } + [UnityTest] + public IEnumerator DelayFrame() => UniTask.ToCoroutine(async () => + { + for (int i = 1; i < 5; i++) + { + await UniTask.Yield(PlayerLoopTiming.PreUpdate); + var frameCount = Time.frameCount; + await UniTask.DelayFrame(i); + Time.frameCount.Should().Be(frameCount + i); + } - // for (int i = 1; i < 5; i++) - // { - // await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); - // var frameCount = Time.frameCount; - // await UniTask.DelayFrame(i); - // Time.frameCount.Should().Be(frameCount + i); - // } - //}); + for (int i = 1; i < 5; i++) + { + await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); + var frameCount = Time.frameCount; + await UniTask.DelayFrame(i); + Time.frameCount.Should().Be(frameCount + i); + } + }); - //[UnityTest] - //public IEnumerator DelayFrameZero() => UniTask.ToCoroutine(async () => - //{ - // { - // await UniTask.Yield(PlayerLoopTiming.PreUpdate); - // var frameCount = Time.frameCount; - // await UniTask.DelayFrame(0); - // Time.frameCount.Should().Be(frameCount); // same frame - // } - // { - // await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); - // var frameCount = Time.frameCount; - // await UniTask.DelayFrame(0); - // Time.frameCount.Should().Be(frameCount + 1); // next frame - // } - //}); + [UnityTest] + public IEnumerator DelayFrameZero() => UniTask.ToCoroutine(async () => + { + { + await UniTask.Yield(PlayerLoopTiming.PreUpdate); + var frameCount = Time.frameCount; + await UniTask.DelayFrame(0); + Time.frameCount.Should().Be(frameCount); // same frame + } + { + await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); + var frameCount = Time.frameCount; + await UniTask.DelayFrame(0); + Time.frameCount.Should().Be(frameCount + 1); // next frame + } + }); - //[UnityTest] - //public IEnumerator TimerFramePre() => UniTask.ToCoroutine(async () => - //{ - // await UniTask.Yield(PlayerLoopTiming.PreUpdate); + [UnityTest] + public IEnumerator TimerFramePre() => UniTask.ToCoroutine(async () => + { + await UniTask.Yield(PlayerLoopTiming.PreUpdate); - // var initialFrame = Time.frameCount; - // var xs = await UniTaskAsyncEnumerable.TimerFrame(2, 3).Take(5).Select(_ => Time.frameCount).ToArrayAsync(); + var initialFrame = Time.frameCount; + var xs = await UniTaskAsyncEnumerable.TimerFrame(2, 3).Take(5).Select(_ => Time.frameCount).ToArrayAsync(); - // xs[0].Should().Be(initialFrame + 2); - // xs[1].Should().Be(initialFrame + 2 + (3 * 1)); - // xs[2].Should().Be(initialFrame + 2 + (3 * 2)); - // xs[3].Should().Be(initialFrame + 2 + (3 * 3)); - // xs[4].Should().Be(initialFrame + 2 + (3 * 4)); - //}); + xs[0].Should().Be(initialFrame + 2); + xs[1].Should().Be(initialFrame + 2 + (3 * 1)); + xs[2].Should().Be(initialFrame + 2 + (3 * 2)); + xs[3].Should().Be(initialFrame + 2 + (3 * 3)); + xs[4].Should().Be(initialFrame + 2 + (3 * 4)); + }); - //[UnityTest] - //public IEnumerator TimerFramePost() => UniTask.ToCoroutine(async () => - //{ - // await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); + [UnityTest] + public IEnumerator TimerFramePost() => UniTask.ToCoroutine(async () => + { + await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); - // var initialFrame = Time.frameCount; - // var xs = await UniTaskAsyncEnumerable.TimerFrame(2, 3).Take(5).Select(_ => Time.frameCount).ToArrayAsync(); + var initialFrame = Time.frameCount; + var xs = await UniTaskAsyncEnumerable.TimerFrame(2, 3).Take(5).Select(_ => Time.frameCount).ToArrayAsync(); - // xs[0].Should().Be(initialFrame + 2); - // xs[1].Should().Be(initialFrame + 2 + (3 * 1)); - // xs[2].Should().Be(initialFrame + 2 + (3 * 2)); - // xs[3].Should().Be(initialFrame + 2 + (3 * 3)); - // xs[4].Should().Be(initialFrame + 2 + (3 * 4)); - //}); + xs[0].Should().Be(initialFrame + 2); + xs[1].Should().Be(initialFrame + 2 + (3 * 1)); + xs[2].Should().Be(initialFrame + 2 + (3 * 2)); + xs[3].Should().Be(initialFrame + 2 + (3 * 3)); + xs[4].Should().Be(initialFrame + 2 + (3 * 4)); + }); - //[UnityTest] - //public IEnumerator TimerFrameTest() => UniTask.ToCoroutine(async () => - //{ - // await UniTask.Yield(PlayerLoopTiming.PreUpdate); + [UnityTest] + public IEnumerator TimerFrameTest() => UniTask.ToCoroutine(async () => + { + await UniTask.Yield(PlayerLoopTiming.PreUpdate); - // var initialFrame = Time.frameCount; - // var xs = await UniTaskAsyncEnumerable.TimerFrame(0, 0).Take(5).Select(_ => Time.frameCount).ToArrayAsync(); + var initialFrame = Time.frameCount; + var xs = await UniTaskAsyncEnumerable.TimerFrame(0, 0).Take(5).Select(_ => Time.frameCount).ToArrayAsync(); - // xs[0].Should().Be(initialFrame); - // xs[1].Should().Be(initialFrame + 1); - // xs[2].Should().Be(initialFrame + 2); - // xs[3].Should().Be(initialFrame + 3); - // xs[4].Should().Be(initialFrame + 4); - //}); + xs[0].Should().Be(initialFrame); + xs[1].Should().Be(initialFrame + 1); + xs[2].Should().Be(initialFrame + 2); + xs[3].Should().Be(initialFrame + 3); + xs[4].Should().Be(initialFrame + 4); + }); - //[UnityTest] - //public IEnumerator TimerFrameSinglePre() => UniTask.ToCoroutine(async () => - //{ - // { - // await UniTask.Yield(PlayerLoopTiming.PreUpdate); - // var initialFrame = Time.frameCount; - // var xs = await UniTaskAsyncEnumerable.TimerFrame(0).Select(_ => Time.frameCount).ToArrayAsync(); - // xs[0].Should().Be(initialFrame); + [UnityTest] + public IEnumerator TimerFrameSinglePre() => UniTask.ToCoroutine(async () => + { + { + await UniTask.Yield(PlayerLoopTiming.PreUpdate); + var initialFrame = Time.frameCount; + var xs = await UniTaskAsyncEnumerable.TimerFrame(0).Select(_ => Time.frameCount).ToArrayAsync(); + xs[0].Should().Be(initialFrame); - // } - // { - // await UniTask.Yield(PlayerLoopTiming.PreUpdate); - // var initialFrame = Time.frameCount; + } + { + await UniTask.Yield(PlayerLoopTiming.PreUpdate); + var initialFrame = Time.frameCount; - // var xs = await UniTaskAsyncEnumerable.TimerFrame(1).Select(_ => - // { - // var t = Time.frameCount; + var xs = await UniTaskAsyncEnumerable.TimerFrame(1).Select(_ => + { + var t = Time.frameCount; - // return t; - // }).ToArrayAsync(); + return t; + }).ToArrayAsync(); - // xs[0].Should().Be(initialFrame + 1); - // } - // { - // await UniTask.Yield(PlayerLoopTiming.PreUpdate); - // var initialFrame = Time.frameCount; - // var xs = await UniTaskAsyncEnumerable.TimerFrame(2).Select(_ => Time.frameCount).ToArrayAsync(); - // xs[0].Should().Be(initialFrame + 2); - // } - //}); + xs[0].Should().Be(initialFrame + 1); + } + { + await UniTask.Yield(PlayerLoopTiming.PreUpdate); + var initialFrame = Time.frameCount; + var xs = await UniTaskAsyncEnumerable.TimerFrame(2).Select(_ => Time.frameCount).ToArrayAsync(); + xs[0].Should().Be(initialFrame + 2); + } + }); - //[UnityTest] - //public IEnumerator TimerFrameSinglePost() => UniTask.ToCoroutine(async () => - //{ - // { - // //await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); - // //var initialFrame = Time.frameCount; - // //var xs = await UniTaskAsyncEnumerable.TimerFrame(0).Select(_ => Time.frameCount).ToArrayAsync(); - // //xs[0].Should().Be(initialFrame); - // } - // { - // //await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); - // var initialFrame = Time.frameCount; - // var xs = await UniTaskAsyncEnumerable.TimerFrame(1).Select(_ => Time.frameCount).ToArrayAsync(); - // xs[0].Should().Be(initialFrame + 1); - // } - // { - // //await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); - // var initialFrame = Time.frameCount; - // var xs = await UniTaskAsyncEnumerable.TimerFrame(2).Select(_ => Time.frameCount).ToArrayAsync(); - // xs[0].Should().Be(initialFrame + 2); - // } - //}); + [UnityTest] + public IEnumerator TimerFrameSinglePost() => UniTask.ToCoroutine(async () => + { + { + //await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); + //var initialFrame = Time.frameCount; + //var xs = await UniTaskAsyncEnumerable.TimerFrame(0).Select(_ => Time.frameCount).ToArrayAsync(); + //xs[0].Should().Be(initialFrame); + } + { + //await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); + var initialFrame = Time.frameCount; + var xs = await UniTaskAsyncEnumerable.TimerFrame(1).Select(_ => Time.frameCount).ToArrayAsync(); + xs[0].Should().Be(initialFrame + 1); + } + { + //await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); + var initialFrame = Time.frameCount; + var xs = await UniTaskAsyncEnumerable.TimerFrame(2).Select(_ => Time.frameCount).ToArrayAsync(); + xs[0].Should().Be(initialFrame + 2); + } + }); - //[UnityTest] - //public IEnumerator Timer() => UniTask.ToCoroutine(async () => - //{ - // await UniTask.Yield(PlayerLoopTiming.PreUpdate); + [UnityTest] + public IEnumerator Timer() => UniTask.ToCoroutine(async () => + { + await UniTask.Yield(PlayerLoopTiming.PreUpdate); - // { - // var initialSeconds = Time.realtimeSinceStartup; - // var xs = await UniTaskAsyncEnumerable.Timer(TimeSpan.FromSeconds(2)).Select(_ => Time.realtimeSinceStartup).ToArrayAsync(); + { + var initialSeconds = Time.realtimeSinceStartup; + var xs = await UniTaskAsyncEnumerable.Timer(TimeSpan.FromSeconds(2)).Select(_ => Time.realtimeSinceStartup).ToArrayAsync(); - // Mathf.Approximately(initialSeconds, xs[0]).Should().BeFalse(); - // Debug.Log("Init:" + initialSeconds); - // Debug.Log("After:" + xs[0]); - // } - //}); + Mathf.Approximately(initialSeconds, xs[0]).Should().BeFalse(); + Debug.Log("Init:" + initialSeconds); + Debug.Log("After:" + xs[0]); + } + }); [UnityTest] @@ -181,17 +181,24 @@ namespace Cysharp.Threading.TasksTests { await UniTask.Run(async () => { - Debug.Log("Go Delay?"); - await UniTask.Delay(TimeSpan.FromSeconds(2)); - - Debug.Log("OK?"); }); }); + [UnityTest] + public IEnumerator DelayRealtime() => UniTask.ToCoroutine(async () => + { + var now = DateTimeOffset.UtcNow; + await UniTask.DelayRealtime(TimeSpan.FromSeconds(2)); + + var elapsed = DateTimeOffset.UtcNow - now; + + var okay1 = TimeSpan.FromSeconds(1.80) <= elapsed; + var okay2 = elapsed <= TimeSpan.FromSeconds(2.20); + + okay1.Should().Be(true); + okay2.Should().Be(true); + }); } - - - -} +} \ No newline at end of file