PlayerLoopTimer unit test and fixes

master
neuecc 2021-03-12 16:06:42 +09:00
parent 49ca9364f7
commit 8b3c8d15c4
6 changed files with 237 additions and 14 deletions

View File

@ -43,6 +43,7 @@
..\UniTask\Assets\Plugins\UniTask\Runtime\EnumeratorAsyncExtensions.cs; ..\UniTask\Assets\Plugins\UniTask\Runtime\EnumeratorAsyncExtensions.cs;
..\UniTask\Assets\Plugins\UniTask\Runtime\TimeoutController.cs; ..\UniTask\Assets\Plugins\UniTask\Runtime\TimeoutController.cs;
..\UniTask\Assets\Plugins\UniTask\Runtime\PlayerLoopHelper.cs; ..\UniTask\Assets\Plugins\UniTask\Runtime\PlayerLoopHelper.cs;
..\UniTask\Assets\Plugins\UniTask\Runtime\PlayerLoopTimer.cs;
..\UniTask\Assets\Plugins\UniTask\Runtime\UniTask.Delay.cs; ..\UniTask\Assets\Plugins\UniTask\Runtime\UniTask.Delay.cs;
..\UniTask\Assets\Plugins\UniTask\Runtime\UniTask.Run.cs; ..\UniTask\Assets\Plugins\UniTask\Runtime\UniTask.Run.cs;
..\UniTask\Assets\Plugins\UniTask\Runtime\UniTask.Bridge.cs; ..\UniTask\Assets\Plugins\UniTask\Runtime\UniTask.Bridge.cs;

View File

@ -15,7 +15,8 @@ namespace Cysharp.Threading.Tasks
readonly PlayerLoopTiming playerLoopTiming; readonly PlayerLoopTiming playerLoopTiming;
readonly bool periodic; readonly bool periodic;
bool isPlaying; bool isRunning;
bool tryStop;
bool isDisposed; bool isDisposed;
protected PlayerLoopTimer(bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state) protected PlayerLoopTimer(bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
@ -64,9 +65,13 @@ namespace Cysharp.Threading.Tasks
if (isDisposed) throw new ObjectDisposedException(null); if (isDisposed) throw new ObjectDisposedException(null);
ResetCore(null); // init state ResetCore(null); // init state
isPlaying = true; if (!isRunning)
{
isRunning = true;
PlayerLoopHelper.AddAction(playerLoopTiming, this); PlayerLoopHelper.AddAction(playerLoopTiming, this);
} }
tryStop = false;
}
/// <summary> /// <summary>
/// Restart(Reset and Start) and change interval. /// Restart(Reset and Start) and change interval.
@ -76,16 +81,20 @@ namespace Cysharp.Threading.Tasks
if (isDisposed) throw new ObjectDisposedException(null); if (isDisposed) throw new ObjectDisposedException(null);
ResetCore(interval); // init state ResetCore(interval); // init state
isPlaying = true; if (!isRunning)
{
isRunning = true;
PlayerLoopHelper.AddAction(playerLoopTiming, this); PlayerLoopHelper.AddAction(playerLoopTiming, this);
} }
tryStop = false;
}
/// <summary> /// <summary>
/// Stop timer. /// Stop timer.
/// </summary> /// </summary>
public void Stop() public void Stop()
{ {
isPlaying = false; tryStop = true;
} }
protected abstract void ResetCore(TimeSpan? newInterval); protected abstract void ResetCore(TimeSpan? newInterval);
@ -97,9 +106,21 @@ namespace Cysharp.Threading.Tasks
bool IPlayerLoopItem.MoveNext() bool IPlayerLoopItem.MoveNext()
{ {
if (isDisposed) return false; if (isDisposed)
if (!isPlaying) return false; {
if (cancellationToken.IsCancellationRequested) return false; isRunning = false;
return false;
}
if (tryStop)
{
isRunning = false;
return false;
}
if (cancellationToken.IsCancellationRequested)
{
isRunning = false;
return false;
}
if (!MoveNextCore()) if (!MoveNextCore())
{ {
@ -112,6 +133,7 @@ namespace Cysharp.Threading.Tasks
} }
else else
{ {
isRunning = false;
return false; return false;
} }
} }
@ -132,7 +154,6 @@ namespace Cysharp.Threading.Tasks
: base(periodic, playerLoopTiming, cancellationToken, timerCallback, state) : base(periodic, playerLoopTiming, cancellationToken, timerCallback, state)
{ {
ResetCore(interval); ResetCore(interval);
this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
} }
protected override bool MoveNextCore() protected override bool MoveNextCore()
@ -157,6 +178,7 @@ namespace Cysharp.Threading.Tasks
protected override void ResetCore(TimeSpan? interval) protected override void ResetCore(TimeSpan? interval)
{ {
this.elapsed = 0.0f; this.elapsed = 0.0f;
this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
if (interval != null) if (interval != null)
{ {
this.interval = (float)interval.Value.TotalSeconds; this.interval = (float)interval.Value.TotalSeconds;
@ -174,7 +196,6 @@ namespace Cysharp.Threading.Tasks
: base(periodic, playerLoopTiming, cancellationToken, timerCallback, state) : base(periodic, playerLoopTiming, cancellationToken, timerCallback, state)
{ {
ResetCore(interval); ResetCore(interval);
this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
} }
protected override bool MoveNextCore() protected override bool MoveNextCore()
@ -198,7 +219,8 @@ namespace Cysharp.Threading.Tasks
protected override void ResetCore(TimeSpan? interval) protected override void ResetCore(TimeSpan? interval)
{ {
elapsed = 0.0f; this.elapsed = 0.0f;
this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
if (interval != null) if (interval != null)
{ {
this.interval = (float)interval.Value.TotalSeconds; this.interval = (float)interval.Value.TotalSeconds;

View File

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

View File

@ -76,14 +76,13 @@ namespace Cysharp.Threading.Tasks
timer = null; timer = null;
} }
var useSource = (linkedSource != null) ? linkedSource : timeoutSource; var useSource = (linkedSource != null) ? linkedSource : timeoutSource;
var token = useSource.Token; var token = useSource.Token;
if (timer == null) if (timer == null)
{ {
// Timer complete => timeoutSource.Cancel() -> linkedSource will be canceled. // Timer complete => timeoutSource.Cancel() -> linkedSource will be canceled.
// (linked)token is canceled => stop timer // (linked)token is canceled => stop timer
timer = PlayerLoopTimer.Create(timeout, false, delayType, delayTiming, token, CancelCancellationTokenSourceStateDelegate, timeoutSource); timer = PlayerLoopTimer.StartNew(timeout, false, delayType, delayTiming, token, CancelCancellationTokenSourceStateDelegate, timeoutSource);
} }
else else
{ {

View File

@ -0,0 +1,179 @@
using Cysharp.Threading.Tasks;
using FluentAssertions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine.TestTools;
namespace Cysharp.Threading.TasksTests
{
public class PlayerLoopTimerTest
{
void Between(TimeSpan l, TimeSpan data, TimeSpan r)
{
NUnit.Framework.Assert.AreEqual(l < data, true, "{0} < {1} failed.", l, data);
NUnit.Framework.Assert.AreEqual(data < r, true, "{0} < {1} failed.", data, r);
}
[UnityTest]
public IEnumerator StandardTicks() => UniTask.ToCoroutine(async () =>
{
foreach (var delay in new[] { DelayType.DeltaTime, DelayType.Realtime, DelayType.UnscaledDeltaTime })
{
var raisedTimeout = new UniTaskCompletionSource();
PlayerLoopTimer.StartNew(TimeSpan.FromSeconds(1), false, delay, PlayerLoopTiming.Update, CancellationToken.None, _ =>
{
raisedTimeout.TrySetResult();
}, null);
var sw = Stopwatch.StartNew();
await raisedTimeout.Task;
sw.Stop();
Between(TimeSpan.FromSeconds(0.9), sw.Elapsed, TimeSpan.FromSeconds(1.1));
}
});
[UnityTest]
public IEnumerator Periodic() => UniTask.ToCoroutine(async () =>
{
var raisedTime = new List<DateTime>();
var count = 0;
var complete = new UniTaskCompletionSource();
PlayerLoopTimer timer = null;
timer = PlayerLoopTimer.StartNew(TimeSpan.FromSeconds(1), true, DelayType.DeltaTime, PlayerLoopTiming.Update, CancellationToken.None, _ =>
{
raisedTime.Add(DateTime.UtcNow);
count++;
if (count == 3)
{
complete.TrySetResult();
timer.Dispose();
}
}, null);
var start = DateTime.UtcNow;
await complete.Task;
Between(TimeSpan.FromSeconds(0.9), raisedTime[0] - start, TimeSpan.FromSeconds(1.1));
Between(TimeSpan.FromSeconds(1.9), raisedTime[1] - start, TimeSpan.FromSeconds(2.1));
Between(TimeSpan.FromSeconds(2.9), raisedTime[2] - start, TimeSpan.FromSeconds(3.1));
});
[UnityTest]
public IEnumerator CancelAfterSlimTest() => UniTask.ToCoroutine(async () =>
{
var cts = new CancellationTokenSource();
var complete = new UniTaskCompletionSource();
cts.Token.RegisterWithoutCaptureExecutionContext(() =>
{
complete.TrySetResult();
});
cts.CancelAfterSlim(TimeSpan.FromSeconds(1));
var sw = Stopwatch.StartNew();
await complete.Task;
Between(TimeSpan.FromSeconds(0.9), sw.Elapsed, TimeSpan.FromSeconds(1.1));
});
[UnityTest]
public IEnumerator CancelAfterSlimCancelTest() => UniTask.ToCoroutine(async () =>
{
var cts = new CancellationTokenSource();
var complete = new UniTaskCompletionSource();
cts.Token.RegisterWithoutCaptureExecutionContext(() =>
{
complete.TrySetResult();
});
var d = cts.CancelAfterSlim(TimeSpan.FromSeconds(1));
var sw = Stopwatch.StartNew();
await UniTask.Delay(TimeSpan.FromMilliseconds(100));
d.Dispose();
await UniTask.Delay(TimeSpan.FromSeconds(2));
complete.Task.Status.Should().Be(UniTaskStatus.Pending);
});
[UnityTest]
public IEnumerator TimeoutController() => UniTask.ToCoroutine(async () =>
{
var controller = new TimeoutController();
var token = controller.Timeout(TimeSpan.FromSeconds(1));
var complete = new UniTaskCompletionSource();
token.RegisterWithoutCaptureExecutionContext(() =>
{
complete.TrySetResult();
});
var sw = Stopwatch.StartNew();
await complete.Task;
Between(TimeSpan.FromSeconds(0.9), sw.Elapsed, TimeSpan.FromSeconds(1.1));
controller.IsTimeout().Should().BeTrue();
});
[UnityTest]
public IEnumerator TimeoutReuse() => UniTask.ToCoroutine(async () =>
{
var controller = new TimeoutController(DelayType.DeltaTime);
var token = controller.Timeout(TimeSpan.FromSeconds(2));
var complete = new UniTaskCompletionSource();
token.RegisterWithoutCaptureExecutionContext(() =>
{
complete.TrySetResult(); // reuse, used same token?
});
await UniTask.Delay(TimeSpan.FromMilliseconds(100));
controller.Reset();
controller.IsTimeout().Should().BeFalse();
var sw = Stopwatch.StartNew();
controller.Timeout(TimeSpan.FromSeconds(5));
await complete.Task;
UnityEngine.Debug.Log(UnityEngine.Time.timeScale);
Between(TimeSpan.FromSeconds(4.9), sw.Elapsed, TimeSpan.FromSeconds(5.1));
controller.IsTimeout().Should().BeTrue();
});
[UnityTest]
public IEnumerator LinkedTokenTest() => UniTask.ToCoroutine(async () =>
{
var cts = new CancellationTokenSource();
var controller = new TimeoutController(cts);
var token = controller.Timeout(TimeSpan.FromSeconds(2));
await UniTask.DelayFrame(3);
cts.Cancel();
token.IsCancellationRequested.Should().BeTrue();
controller.Dispose();
});
}
}

View File

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