PlayerLoopTimer unit test and fixes
parent
49ca9364f7
commit
8b3c8d15c4
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 57095a17fdca7ee4380450910afc7f26
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c0c49de697f829f44aa8709b4d1eff3e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
Loading…
Reference in New Issue