TimeoutController uses PlayerLoopTimer(WIP, needs test)
parent
62f6429b60
commit
49ca9364f7
|
@ -39,8 +39,6 @@ namespace Cysharp.Threading.Tasks
|
|||
var trigger = gameObject.GetAsyncDestroyTrigger();
|
||||
trigger.CancellationToken.RegisterWithoutCaptureExecutionContext(CancelCancellationTokenSourceStateDelegate, cts);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,23 +27,31 @@ namespace Cysharp.Threading.Tasks
|
|||
this.state = state;
|
||||
}
|
||||
|
||||
public static PlayerLoopTimer Create(TimeSpan delayTimeSpan, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
|
||||
public static PlayerLoopTimer Create(TimeSpan interval, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// force use Realtime.
|
||||
if (PlayerLoopHelper.IsMainThread && !UnityEditor.EditorApplication.isPlaying)
|
||||
{
|
||||
delayType = DelayType.Realtime;
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (delayType)
|
||||
{
|
||||
case DelayType.UnscaledDeltaTime:
|
||||
return new IgnoreTimeScalePlayerLoopTimer(delayTimeSpan, periodic, playerLoopTiming, cancellationToken, timerCallback, state);
|
||||
return new IgnoreTimeScalePlayerLoopTimer(interval, periodic, playerLoopTiming, cancellationToken, timerCallback, state);
|
||||
case DelayType.Realtime:
|
||||
return new RealtimePlayerLoopTimer(delayTimeSpan, periodic, playerLoopTiming, cancellationToken, timerCallback, state);
|
||||
return new RealtimePlayerLoopTimer(interval, periodic, playerLoopTiming, cancellationToken, timerCallback, state);
|
||||
case DelayType.DeltaTime:
|
||||
default:
|
||||
return new DeltaTimePlayerLoopTimer(delayTimeSpan, periodic, playerLoopTiming, cancellationToken, timerCallback, state);
|
||||
return new DeltaTimePlayerLoopTimer(interval, periodic, playerLoopTiming, cancellationToken, timerCallback, state);
|
||||
}
|
||||
}
|
||||
|
||||
public static PlayerLoopTimer StartNew(TimeSpan delayTimeSpan, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
|
||||
public static PlayerLoopTimer StartNew(TimeSpan interval, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
|
||||
{
|
||||
var timer = Create(delayTimeSpan, periodic, delayType, playerLoopTiming, cancellationToken, timerCallback, state);
|
||||
var timer = Create(interval, periodic, delayType, playerLoopTiming, cancellationToken, timerCallback, state);
|
||||
timer.Restart();
|
||||
return timer;
|
||||
}
|
||||
|
@ -55,7 +63,19 @@ namespace Cysharp.Threading.Tasks
|
|||
{
|
||||
if (isDisposed) throw new ObjectDisposedException(null);
|
||||
|
||||
ResetCore(); // init state
|
||||
ResetCore(null); // init state
|
||||
isPlaying = true;
|
||||
PlayerLoopHelper.AddAction(playerLoopTiming, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restart(Reset and Start) and change interval.
|
||||
/// </summary>
|
||||
public void Restart(TimeSpan interval)
|
||||
{
|
||||
if (isDisposed) throw new ObjectDisposedException(null);
|
||||
|
||||
ResetCore(interval); // init state
|
||||
isPlaying = true;
|
||||
PlayerLoopHelper.AddAction(playerLoopTiming, this);
|
||||
}
|
||||
|
@ -68,7 +88,7 @@ namespace Cysharp.Threading.Tasks
|
|||
isPlaying = false;
|
||||
}
|
||||
|
||||
protected abstract void ResetCore();
|
||||
protected abstract void ResetCore(TimeSpan? newInterval);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -87,7 +107,7 @@ namespace Cysharp.Threading.Tasks
|
|||
|
||||
if (periodic)
|
||||
{
|
||||
ResetCore();
|
||||
ResetCore(null);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
@ -106,13 +126,12 @@ namespace Cysharp.Threading.Tasks
|
|||
{
|
||||
int initialFrame;
|
||||
float elapsed;
|
||||
readonly float delayTimeSpan;
|
||||
float interval;
|
||||
|
||||
public DeltaTimePlayerLoopTimer(TimeSpan delayTimeSpan, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
|
||||
public DeltaTimePlayerLoopTimer(TimeSpan interval, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
|
||||
: base(periodic, playerLoopTiming, cancellationToken, timerCallback, state)
|
||||
{
|
||||
this.elapsed = 0.0f;
|
||||
this.delayTimeSpan = (float)delayTimeSpan.TotalSeconds;
|
||||
ResetCore(interval);
|
||||
this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
|
||||
}
|
||||
|
||||
|
@ -127,7 +146,7 @@ namespace Cysharp.Threading.Tasks
|
|||
}
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
if (elapsed >= delayTimeSpan)
|
||||
if (elapsed >= interval)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -135,9 +154,13 @@ namespace Cysharp.Threading.Tasks
|
|||
return true;
|
||||
}
|
||||
|
||||
protected override void ResetCore()
|
||||
protected override void ResetCore(TimeSpan? interval)
|
||||
{
|
||||
elapsed = 0.0f;
|
||||
this.elapsed = 0.0f;
|
||||
if (interval != null)
|
||||
{
|
||||
this.interval = (float)interval.Value.TotalSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,13 +168,12 @@ namespace Cysharp.Threading.Tasks
|
|||
{
|
||||
int initialFrame;
|
||||
float elapsed;
|
||||
readonly float delayTimeSpan;
|
||||
float interval;
|
||||
|
||||
public IgnoreTimeScalePlayerLoopTimer(TimeSpan delayTimeSpan, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
|
||||
public IgnoreTimeScalePlayerLoopTimer(TimeSpan interval, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
|
||||
: base(periodic, playerLoopTiming, cancellationToken, timerCallback, state)
|
||||
{
|
||||
this.elapsed = 0.0f;
|
||||
this.delayTimeSpan = (float)delayTimeSpan.TotalSeconds;
|
||||
ResetCore(interval);
|
||||
this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
|
||||
}
|
||||
|
||||
|
@ -166,7 +188,7 @@ namespace Cysharp.Threading.Tasks
|
|||
}
|
||||
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
if (elapsed >= delayTimeSpan)
|
||||
if (elapsed >= interval)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -174,27 +196,30 @@ namespace Cysharp.Threading.Tasks
|
|||
return true;
|
||||
}
|
||||
|
||||
protected override void ResetCore()
|
||||
protected override void ResetCore(TimeSpan? interval)
|
||||
{
|
||||
elapsed = 0.0f;
|
||||
if (interval != null)
|
||||
{
|
||||
this.interval = (float)interval.Value.TotalSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class RealtimePlayerLoopTimer : PlayerLoopTimer
|
||||
{
|
||||
ValueStopwatch stopwatch;
|
||||
readonly long delayTimeSpanTicks;
|
||||
long intervalTicks;
|
||||
|
||||
public RealtimePlayerLoopTimer(TimeSpan delayTimeSpan, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
|
||||
public RealtimePlayerLoopTimer(TimeSpan interval, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
|
||||
: base(periodic, playerLoopTiming, cancellationToken, timerCallback, state)
|
||||
{
|
||||
this.stopwatch = ValueStopwatch.StartNew();
|
||||
this.delayTimeSpanTicks = delayTimeSpan.Ticks;
|
||||
ResetCore(interval);
|
||||
}
|
||||
|
||||
protected override bool MoveNextCore()
|
||||
{
|
||||
if (stopwatch.ElapsedTicks >= delayTimeSpanTicks)
|
||||
if (stopwatch.ElapsedTicks >= intervalTicks)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -202,9 +227,13 @@ namespace Cysharp.Threading.Tasks
|
|||
return true;
|
||||
}
|
||||
|
||||
protected override void ResetCore()
|
||||
protected override void ResetCore(TimeSpan? interval)
|
||||
{
|
||||
this.stopwatch = ValueStopwatch.StartNew();
|
||||
if (interval != null)
|
||||
{
|
||||
this.intervalTicks = interval.Value.Ticks;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System.Threading;
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks.Internal;
|
||||
using System.Threading;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
|
@ -14,26 +13,44 @@ namespace Cysharp.Threading.Tasks
|
|||
|
||||
public sealed class TimeoutController : IDisposable
|
||||
{
|
||||
readonly static Action<object> CancelCancellationTokenSourceStateDelegate = new Action<object>(CancelCancellationTokenSourceState);
|
||||
|
||||
static void CancelCancellationTokenSourceState(object state)
|
||||
{
|
||||
var cts = (CancellationTokenSource)state;
|
||||
cts.Cancel();
|
||||
}
|
||||
|
||||
CancellationTokenSource timeoutSource;
|
||||
CancellationTokenSource linkedSource;
|
||||
StoppableDelayRealtimePromise timeoutDelay;
|
||||
PlayerLoopTimer timer;
|
||||
bool isDisposed;
|
||||
|
||||
readonly DelayType delayType;
|
||||
readonly PlayerLoopTiming delayTiming;
|
||||
readonly CancellationTokenSource originalLinkCancellationTokenSource;
|
||||
|
||||
public TimeoutController()
|
||||
public TimeoutController(DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
|
||||
{
|
||||
this.timeoutSource = new CancellationTokenSource();
|
||||
this.originalLinkCancellationTokenSource = null;
|
||||
this.linkedSource = null;
|
||||
this.timeoutDelay = null;
|
||||
this.delayType = delayType;
|
||||
this.delayTiming = delayTiming;
|
||||
}
|
||||
|
||||
public TimeoutController(CancellationTokenSource linkCancellationTokenSource)
|
||||
public TimeoutController(CancellationTokenSource linkCancellationTokenSource, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
|
||||
{
|
||||
this.timeoutSource = new CancellationTokenSource();
|
||||
this.originalLinkCancellationTokenSource = linkCancellationTokenSource;
|
||||
this.linkedSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, linkCancellationTokenSource.Token);
|
||||
this.timeoutDelay = null;
|
||||
this.delayType = delayType;
|
||||
this.delayTiming = delayTiming;
|
||||
}
|
||||
|
||||
public CancellationToken Timeout(int millisecondsTimeout)
|
||||
{
|
||||
return Timeout(TimeSpan.FromMilliseconds(millisecondsTimeout));
|
||||
}
|
||||
|
||||
public CancellationToken Timeout(TimeSpan timeout)
|
||||
|
@ -43,6 +60,7 @@ namespace Cysharp.Threading.Tasks
|
|||
return originalLinkCancellationTokenSource.Token;
|
||||
}
|
||||
|
||||
// Timeouted, create new source and timer.
|
||||
if (timeoutSource.IsCancellationRequested)
|
||||
{
|
||||
timeoutSource.Dispose();
|
||||
|
@ -53,18 +71,26 @@ namespace Cysharp.Threading.Tasks
|
|||
this.linkedSource.Dispose();
|
||||
this.linkedSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, originalLinkCancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
timer?.Dispose();
|
||||
timer = null;
|
||||
}
|
||||
|
||||
if (timeoutDelay == null)
|
||||
|
||||
var useSource = (linkedSource != null) ? linkedSource : timeoutSource;
|
||||
var token = useSource.Token;
|
||||
if (timer == null)
|
||||
{
|
||||
RunDelayAsync(timeout).Forget(); // timeoutDelay = ... in RunDelayAsync(immediately, before await)
|
||||
// Timer complete => timeoutSource.Cancel() -> linkedSource will be canceled.
|
||||
// (linked)token is canceled => stop timer
|
||||
timer = PlayerLoopTimer.Create(timeout, false, delayType, delayTiming, token, CancelCancellationTokenSourceStateDelegate, timeoutSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
timeoutDelay.RestartStopwatch(); // already running RunDelayAsync
|
||||
timer.Restart(timeout);
|
||||
}
|
||||
|
||||
return (linkedSource != null) ? linkedSource.Token : timeoutSource.Token;
|
||||
return token;
|
||||
}
|
||||
|
||||
public bool IsTimeout()
|
||||
|
@ -74,184 +100,30 @@ namespace Cysharp.Threading.Tasks
|
|||
|
||||
public void Reset()
|
||||
{
|
||||
if (timeoutDelay != null)
|
||||
{
|
||||
timeoutDelay.Stop(); // stop delay, will finish RunDelayAsync
|
||||
timeoutDelay = null;
|
||||
}
|
||||
}
|
||||
|
||||
async UniTaskVoid RunDelayAsync(TimeSpan timeout)
|
||||
{
|
||||
timeoutDelay = StoppableDelayRealtimePromise.Create(timeout, PlayerLoopTiming.Update, (linkedSource == null) ? CancellationToken.None : linkedSource.Token, out var version);
|
||||
try
|
||||
{
|
||||
var reason = await new UniTask<DelayResult>(timeoutDelay, version);
|
||||
if (reason == DelayResult.DelayCompleted)
|
||||
{
|
||||
// UnityEngine.Debug.Log("DEBUG:Timeout Complete, try to call timeoutSource.Cancel");
|
||||
timeoutSource.Cancel();
|
||||
}
|
||||
else if (reason == DelayResult.LinkedTokenCanceled)
|
||||
{
|
||||
// UnityEngine.Debug.Log("DEBUG:LinkedSource IsCancellationRequested");
|
||||
}
|
||||
else if (reason == DelayResult.ExternalStopped)
|
||||
{
|
||||
// Reset(Promise.Stop) called, do nothing.
|
||||
// UnityEngine.Debug.Log("DEBUG:Reset called");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
timeoutDelay = null;
|
||||
}
|
||||
timer.Stop();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (timeoutDelay != null)
|
||||
if (isDisposed) return;
|
||||
|
||||
try
|
||||
{
|
||||
timeoutDelay.Stop();
|
||||
}
|
||||
// stop timer.
|
||||
timer.Dispose();
|
||||
|
||||
// cancel and dispose.
|
||||
timeoutSource.Cancel();
|
||||
timeoutSource.Dispose();
|
||||
if (linkedSource != null)
|
||||
{
|
||||
linkedSource.Cancel();
|
||||
linkedSource.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
enum DelayResult
|
||||
{
|
||||
LinkedTokenCanceled,
|
||||
ExternalStopped,
|
||||
DelayCompleted, // as Timeout.
|
||||
}
|
||||
|
||||
// Stop + SuppressCancellationThrow.
|
||||
sealed class StoppableDelayRealtimePromise : IUniTaskSource<DelayResult>, IPlayerLoopItem, ITaskPoolNode<StoppableDelayRealtimePromise>
|
||||
{
|
||||
static OperationCanceledException ExterenalStopException = new OperationCanceledException();
|
||||
|
||||
static TaskPool<StoppableDelayRealtimePromise> pool;
|
||||
StoppableDelayRealtimePromise nextNode;
|
||||
public ref StoppableDelayRealtimePromise NextNode => ref nextNode;
|
||||
|
||||
static StoppableDelayRealtimePromise()
|
||||
{
|
||||
TaskPool.RegisterSizeGetter(typeof(StoppableDelayRealtimePromise), () => pool.Size);
|
||||
}
|
||||
|
||||
long delayTimeSpanTicks;
|
||||
ValueStopwatch stopwatch;
|
||||
CancellationToken cancellationToken;
|
||||
bool externalStop;
|
||||
|
||||
UniTaskCompletionSourceCore<DelayResult> core;
|
||||
|
||||
StoppableDelayRealtimePromise()
|
||||
{
|
||||
}
|
||||
|
||||
public static StoppableDelayRealtimePromise Create(TimeSpan delayTimeSpan, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token)
|
||||
{
|
||||
if (!pool.TryPop(out var result))
|
||||
{
|
||||
result = new StoppableDelayRealtimePromise();
|
||||
}
|
||||
|
||||
result.stopwatch = ValueStopwatch.StartNew();
|
||||
result.delayTimeSpanTicks = delayTimeSpan.Ticks;
|
||||
result.cancellationToken = cancellationToken;
|
||||
result.externalStop = false;
|
||||
|
||||
TaskTracker.TrackActiveTask(result, 3);
|
||||
|
||||
PlayerLoopHelper.AddAction(timing, result);
|
||||
|
||||
token = result.core.Version;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
externalStop = true;
|
||||
}
|
||||
|
||||
public void RestartStopwatch()
|
||||
{
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
}
|
||||
|
||||
public DelayResult GetResult(short token)
|
||||
{
|
||||
try
|
||||
{
|
||||
return core.GetResult(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
TryReturn();
|
||||
}
|
||||
}
|
||||
|
||||
void IUniTaskSource.GetResult(short token)
|
||||
{
|
||||
GetResult(token);
|
||||
}
|
||||
|
||||
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.TrySetResult(DelayResult.LinkedTokenCanceled);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (externalStop)
|
||||
{
|
||||
core.TrySetResult(DelayResult.ExternalStopped);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stopwatch.IsInvalid)
|
||||
{
|
||||
core.TrySetResult(DelayResult.DelayCompleted);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stopwatch.ElapsedTicks >= delayTimeSpanTicks)
|
||||
{
|
||||
core.TrySetResult(DelayResult.DelayCompleted);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryReturn()
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.Reset();
|
||||
stopwatch = default;
|
||||
cancellationToken = default;
|
||||
externalStop = false;
|
||||
return pool.TryPush(this);
|
||||
isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue