add PlayerLoopTimer

master
neuecc 2021-03-12 11:35:46 +09:00
parent b6a9836e81
commit 62f6429b60
2 changed files with 223 additions and 215 deletions

View File

@ -9,28 +9,24 @@ using Cysharp.Threading.Tasks.Internal;
namespace Cysharp.Threading.Tasks
{
public static class CancellationTokenSourceExtensions
public static partial class CancellationTokenSourceExtensions
{
public static void CancelAfterSlim(this CancellationTokenSource cts, int millisecondsDelay, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
readonly static Action<object> CancelCancellationTokenSourceStateDelegate = new Action<object>(CancelCancellationTokenSourceState);
static void CancelCancellationTokenSourceState(object state)
{
var delay = UniTask.Delay(millisecondsDelay, delayType, delayTiming, cts.Token);
CancelAfterCore(cts, delay).Forget();
var cts = (CancellationTokenSource)state;
cts.Cancel();
}
public static void CancelAfterSlim(this CancellationTokenSource cts, TimeSpan delayTimeSpan, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
public static IDisposable CancelAfterSlim(this CancellationTokenSource cts, int millisecondsDelay, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
{
var delay = UniTask.Delay(delayTimeSpan, delayType, delayTiming, cts.Token);
CancelAfterCore(cts, delay).Forget();
return CancelAfterSlim(cts, TimeSpan.FromMilliseconds(millisecondsDelay), delayType, delayTiming);
}
static async UniTaskVoid CancelAfterCore(CancellationTokenSource cts, UniTask delayTask)
public static IDisposable CancelAfterSlim(this CancellationTokenSource cts, TimeSpan delayTimeSpan, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
{
var alreadyCanceled = await delayTask.SuppressCancellationThrow();
if (!alreadyCanceled)
{
cts.Cancel();
cts.Dispose();
}
return PlayerLoopTimer.StartNew(delayTimeSpan, false, delayType, delayTiming, cts.Token, CancelCancellationTokenSourceStateDelegate, cts);
}
public static void RegisterRaiseCancelOnDestroy(this CancellationTokenSource cts, Component component)
@ -41,209 +37,10 @@ namespace Cysharp.Threading.Tasks
public static void RegisterRaiseCancelOnDestroy(this CancellationTokenSource cts, GameObject gameObject)
{
var trigger = gameObject.GetAsyncDestroyTrigger();
trigger.CancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var cts2 = (CancellationTokenSource)state;
cts2.Cancel();
}, cts);
trigger.CancellationToken.RegisterWithoutCaptureExecutionContext(CancelCancellationTokenSourceStateDelegate, cts);
}
// TODO:delete this.
public static IDisposable CancelAfterSlim2(this CancellationTokenSource cts, TimeSpan delayTimeSpan)
{
var timer = PlayerLoopTimer.Create(delayTimeSpan, DelayType.DeltaTime, PlayerLoopTiming.Update, cts.Token, state =>
{
var x = (CancellationTokenSource)state;
x.Cancel();
}, cts);
timer.Restart();
return timer;
}
abstract class PlayerLoopTimer : IDisposable, IPlayerLoopItem
{
readonly CancellationToken cancellationToken;
readonly Action<object> timerCallback;
readonly object state;
readonly PlayerLoopTiming playerLoopTiming;
bool isPlaying;
bool isDisposed;
protected PlayerLoopTimer(PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
{
this.playerLoopTiming = playerLoopTiming;
this.cancellationToken = cancellationToken;
this.timerCallback = timerCallback;
this.state = state;
}
public static PlayerLoopTimer Create(TimeSpan delayTimeSpan, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
{
switch (delayType)
{
case DelayType.UnscaledDeltaTime:
return new IgnoreTimeScalePlayerLoopTimer(delayTimeSpan, playerLoopTiming, cancellationToken, timerCallback, state);
case DelayType.Realtime:
return new RealtimePlayerLoopTimer(delayTimeSpan, playerLoopTiming, cancellationToken, timerCallback, state);
case DelayType.DeltaTime:
default:
return new DeltaTimePlayerLoopTimer(delayTimeSpan, playerLoopTiming, cancellationToken, timerCallback, state);
}
}
/// <summary>
/// Restart(Reset and Start) timer.
/// </summary>
public void Restart()
{
if (isDisposed) throw new ObjectDisposedException(null);
if (isPlaying) return;
ResetCore(); // init state
isPlaying = true;
PlayerLoopHelper.AddAction(playerLoopTiming, this);
}
/// <summary>
/// Stop timer.
/// </summary>
public void Stop()
{
isPlaying = false;
}
protected abstract void ResetCore();
public void Dispose()
{
isDisposed = true;
}
bool IPlayerLoopItem.MoveNext()
{
if (isDisposed) return false;
if (!isPlaying) return false;
if (cancellationToken.IsCancellationRequested) return false;
if (!MoveNextCore())
{
timerCallback(state);
return false;
}
return true;
}
protected abstract bool MoveNextCore();
}
sealed class DeltaTimePlayerLoopTimer : PlayerLoopTimer
{
int initialFrame;
float elapsed;
readonly float delayTimeSpan;
public DeltaTimePlayerLoopTimer(TimeSpan delayTimeSpan, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
: base(playerLoopTiming, cancellationToken, timerCallback, state)
{
this.elapsed = 0.0f;
this.delayTimeSpan = (float)delayTimeSpan.TotalSeconds;
this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
}
protected override bool MoveNextCore()
{
if (elapsed == 0.0f)
{
if (initialFrame == Time.frameCount)
{
return true;
}
}
elapsed += Time.deltaTime;
if (elapsed >= delayTimeSpan)
{
return false;
}
return true;
}
protected override void ResetCore()
{
elapsed = 0.0f;
}
}
sealed class IgnoreTimeScalePlayerLoopTimer : PlayerLoopTimer
{
int initialFrame;
float elapsed;
readonly float delayTimeSpan;
public IgnoreTimeScalePlayerLoopTimer(TimeSpan delayTimeSpan, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
: base(playerLoopTiming, cancellationToken, timerCallback, state)
{
this.elapsed = 0.0f;
this.delayTimeSpan = (float)delayTimeSpan.TotalSeconds;
this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
}
protected override bool MoveNextCore()
{
if (elapsed == 0.0f)
{
if (initialFrame == Time.frameCount)
{
return true;
}
}
elapsed += Time.unscaledDeltaTime;
if (elapsed >= delayTimeSpan)
{
return false;
}
return true;
}
protected override void ResetCore()
{
elapsed = 0.0f;
}
}
sealed class RealtimePlayerLoopTimer : PlayerLoopTimer
{
ValueStopwatch stopwatch;
readonly long delayTimeSpanTicks;
public RealtimePlayerLoopTimer(TimeSpan delayTimeSpan, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
: base(playerLoopTiming, cancellationToken, timerCallback, state)
{
this.stopwatch = ValueStopwatch.StartNew();
this.delayTimeSpanTicks = delayTimeSpan.Ticks;
}
protected override bool MoveNextCore()
{
if (stopwatch.ElapsedTicks >= delayTimeSpanTicks)
{
return false;
}
return true;
}
protected override void ResetCore()
{
this.stopwatch = ValueStopwatch.StartNew();
}
}
}
}

View File

@ -0,0 +1,211 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using System.Threading;
using System;
using Cysharp.Threading.Tasks.Internal;
using UnityEngine;
namespace Cysharp.Threading.Tasks
{
public abstract class PlayerLoopTimer : IDisposable, IPlayerLoopItem
{
readonly CancellationToken cancellationToken;
readonly Action<object> timerCallback;
readonly object state;
readonly PlayerLoopTiming playerLoopTiming;
readonly bool periodic;
bool isPlaying;
bool isDisposed;
protected PlayerLoopTimer(bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
{
this.periodic = periodic;
this.playerLoopTiming = playerLoopTiming;
this.cancellationToken = cancellationToken;
this.timerCallback = timerCallback;
this.state = state;
}
public static PlayerLoopTimer Create(TimeSpan delayTimeSpan, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
{
switch (delayType)
{
case DelayType.UnscaledDeltaTime:
return new IgnoreTimeScalePlayerLoopTimer(delayTimeSpan, periodic, playerLoopTiming, cancellationToken, timerCallback, state);
case DelayType.Realtime:
return new RealtimePlayerLoopTimer(delayTimeSpan, periodic, playerLoopTiming, cancellationToken, timerCallback, state);
case DelayType.DeltaTime:
default:
return new DeltaTimePlayerLoopTimer(delayTimeSpan, periodic, playerLoopTiming, cancellationToken, timerCallback, state);
}
}
public static PlayerLoopTimer StartNew(TimeSpan delayTimeSpan, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
{
var timer = Create(delayTimeSpan, periodic, delayType, playerLoopTiming, cancellationToken, timerCallback, state);
timer.Restart();
return timer;
}
/// <summary>
/// Restart(Reset and Start) timer.
/// </summary>
public void Restart()
{
if (isDisposed) throw new ObjectDisposedException(null);
ResetCore(); // init state
isPlaying = true;
PlayerLoopHelper.AddAction(playerLoopTiming, this);
}
/// <summary>
/// Stop timer.
/// </summary>
public void Stop()
{
isPlaying = false;
}
protected abstract void ResetCore();
public void Dispose()
{
isDisposed = true;
}
bool IPlayerLoopItem.MoveNext()
{
if (isDisposed) return false;
if (!isPlaying) return false;
if (cancellationToken.IsCancellationRequested) return false;
if (!MoveNextCore())
{
timerCallback(state);
if (periodic)
{
ResetCore();
return true;
}
else
{
return false;
}
}
return true;
}
protected abstract bool MoveNextCore();
}
sealed class DeltaTimePlayerLoopTimer : PlayerLoopTimer
{
int initialFrame;
float elapsed;
readonly float delayTimeSpan;
public DeltaTimePlayerLoopTimer(TimeSpan delayTimeSpan, 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;
this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
}
protected override bool MoveNextCore()
{
if (elapsed == 0.0f)
{
if (initialFrame == Time.frameCount)
{
return true;
}
}
elapsed += Time.deltaTime;
if (elapsed >= delayTimeSpan)
{
return false;
}
return true;
}
protected override void ResetCore()
{
elapsed = 0.0f;
}
}
sealed class IgnoreTimeScalePlayerLoopTimer : PlayerLoopTimer
{
int initialFrame;
float elapsed;
readonly float delayTimeSpan;
public IgnoreTimeScalePlayerLoopTimer(TimeSpan delayTimeSpan, 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;
this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
}
protected override bool MoveNextCore()
{
if (elapsed == 0.0f)
{
if (initialFrame == Time.frameCount)
{
return true;
}
}
elapsed += Time.unscaledDeltaTime;
if (elapsed >= delayTimeSpan)
{
return false;
}
return true;
}
protected override void ResetCore()
{
elapsed = 0.0f;
}
}
sealed class RealtimePlayerLoopTimer : PlayerLoopTimer
{
ValueStopwatch stopwatch;
readonly long delayTimeSpanTicks;
public RealtimePlayerLoopTimer(TimeSpan delayTimeSpan, 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;
}
protected override bool MoveNextCore()
{
if (stopwatch.ElapsedTicks >= delayTimeSpanTicks)
{
return false;
}
return true;
}
protected override void ResetCore()
{
this.stopwatch = ValueStopwatch.StartNew();
}
}
}