Add UniTask.DelayRealtime

pull/115/head
neuecc 2020-06-24 01:40:43 +09:00
parent 93df9d7693
commit 12b39c6ba1
4 changed files with 305 additions and 144 deletions

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f16fb466974ad034c8732c79c7fd67ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +1,7 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using Cysharp.Threading.Tasks.Internal;
using System; using System;
using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using UnityEngine; using UnityEngine;
@ -96,6 +96,16 @@ namespace Cysharp.Threading.Tasks
: new UniTask(DelayPromise.Create(delayTimeSpan, delayTiming, cancellationToken, out token), token); : 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<YieldPromise> sealed class YieldPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<YieldPromise>
{ {
static TaskPool<YieldPromise> pool; static TaskPool<YieldPromise> pool;
@ -404,7 +414,7 @@ namespace Cysharp.Threading.Tasks
} }
int initialFrame; int initialFrame;
float delayFrameTimeSpan; float delayTimeSpan;
float elapsed; float elapsed;
CancellationToken cancellationToken; 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) if (cancellationToken.IsCancellationRequested)
{ {
@ -427,7 +437,7 @@ namespace Cysharp.Threading.Tasks
} }
result.elapsed = 0.0f; result.elapsed = 0.0f;
result.delayFrameTimeSpan = (float)delayFrameTimeSpan.TotalSeconds; result.delayTimeSpan = (float)delayTimeSpan.TotalSeconds;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
result.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; result.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
@ -483,7 +493,7 @@ namespace Cysharp.Threading.Tasks
} }
elapsed += Time.deltaTime; elapsed += Time.deltaTime;
if (elapsed >= delayFrameTimeSpan) if (elapsed >= delayTimeSpan)
{ {
core.TrySetResult(null); core.TrySetResult(null);
return false; return false;
@ -496,7 +506,7 @@ namespace Cysharp.Threading.Tasks
{ {
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
delayFrameTimeSpan = default; delayTimeSpan = default;
elapsed = default; elapsed = default;
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this); return pool.TryPush(this);
@ -612,6 +622,104 @@ namespace Cysharp.Threading.Tasks
return pool.TryPush(this); return pool.TryPush(this);
} }
} }
sealed class DelayRealtimePromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<DelayRealtimePromise>
{
static TaskPool<DelayRealtimePromise> pool;
public DelayRealtimePromise NextNode { get; set; }
static DelayRealtimePromise()
{
TaskPool.RegisterSizeGetter(typeof(DelayRealtimePromise), () => pool.Size);
}
long delayTimeSpanTicks;
ValueStopwatch stopwatch;
CancellationToken cancellationToken;
UniTaskCompletionSourceCore<AsyncUnit> 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<object> 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 public readonly struct YieldAwaitable

View File

@ -16,164 +16,164 @@ namespace Cysharp.Threading.TasksTests
{ {
public class DelayTest public class DelayTest
{ {
//[UnityTest] [UnityTest]
//public IEnumerator DelayFrame() => UniTask.ToCoroutine(async () => public IEnumerator DelayFrame() => UniTask.ToCoroutine(async () =>
//{ {
// for (int i = 1; i < 5; i++) for (int i = 1; i < 5; i++)
// { {
// await UniTask.Yield(PlayerLoopTiming.PreUpdate); await UniTask.Yield(PlayerLoopTiming.PreUpdate);
// var frameCount = Time.frameCount; var frameCount = Time.frameCount;
// await UniTask.DelayFrame(i); await UniTask.DelayFrame(i);
// Time.frameCount.Should().Be(frameCount + i); Time.frameCount.Should().Be(frameCount + i);
// } }
// for (int i = 1; i < 5; 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 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();
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);
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));
});
[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();
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);
}
{
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
var initialFrame = Time.frameCount;
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).Select(_ =>
{
var t = Time.frameCount;
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);
}
});
[UnityTest]
public IEnumerator TimerFrameSinglePost() => UniTask.ToCoroutine(async () =>
{
{
//await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); //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 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();
// 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);
// 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));
//});
//[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();
// 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 initialFrame = Time.frameCount;
//var xs = await UniTaskAsyncEnumerable.TimerFrame(0).Select(_ => Time.frameCount).ToArrayAsync(); //var xs = await UniTaskAsyncEnumerable.TimerFrame(0).Select(_ => Time.frameCount).ToArrayAsync();
//xs[0].Should().Be(initialFrame); //xs[0].Should().Be(initialFrame);
}
// } {
// { //await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
// await UniTask.Yield(PlayerLoopTiming.PreUpdate); var initialFrame = Time.frameCount;
// var initialFrame = Time.frameCount; var xs = await UniTaskAsyncEnumerable.TimerFrame(1).Select(_ => Time.frameCount).ToArrayAsync();
xs[0].Should().Be(initialFrame + 1);
// var xs = await UniTaskAsyncEnumerable.TimerFrame(1).Select(_ => }
// { {
// var t = Time.frameCount; //await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
var initialFrame = Time.frameCount;
// return t; var xs = await UniTaskAsyncEnumerable.TimerFrame(2).Select(_ => Time.frameCount).ToArrayAsync();
// }).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] [UnityTest]
//public IEnumerator Timer() => UniTask.ToCoroutine(async () => public IEnumerator Timer() => UniTask.ToCoroutine(async () =>
//{ {
// await UniTask.Yield(PlayerLoopTiming.PreUpdate); await UniTask.Yield(PlayerLoopTiming.PreUpdate);
// { {
// var initialSeconds = Time.realtimeSinceStartup; var initialSeconds = Time.realtimeSinceStartup;
// var xs = await UniTaskAsyncEnumerable.Timer(TimeSpan.FromSeconds(2)).Select(_ => Time.realtimeSinceStartup).ToArrayAsync(); var xs = await UniTaskAsyncEnumerable.Timer(TimeSpan.FromSeconds(2)).Select(_ => Time.realtimeSinceStartup).ToArrayAsync();
// Mathf.Approximately(initialSeconds, xs[0]).Should().BeFalse(); Mathf.Approximately(initialSeconds, xs[0]).Should().BeFalse();
// Debug.Log("Init:" + initialSeconds); Debug.Log("Init:" + initialSeconds);
// Debug.Log("After:" + xs[0]); Debug.Log("After:" + xs[0]);
// } }
//}); });
[UnityTest] [UnityTest]
@ -181,17 +181,24 @@ namespace Cysharp.Threading.TasksTests
{ {
await UniTask.Run(async () => await UniTask.Run(async () =>
{ {
Debug.Log("Go Delay?");
await UniTask.Delay(TimeSpan.FromSeconds(2)); 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);
});
} }
} }