#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 timerCallback; readonly object state; readonly PlayerLoopTiming playerLoopTiming; readonly bool periodic; bool isRunning; bool tryStop; bool isDisposed; protected PlayerLoopTimer(bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) { this.periodic = periodic; this.playerLoopTiming = playerLoopTiming; this.cancellationToken = cancellationToken; this.timerCallback = timerCallback; this.state = state; } public static PlayerLoopTimer Create(TimeSpan interval, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action 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(interval, periodic, playerLoopTiming, cancellationToken, timerCallback, state); case DelayType.Realtime: return new RealtimePlayerLoopTimer(interval, periodic, playerLoopTiming, cancellationToken, timerCallback, state); case DelayType.DeltaTime: default: return new DeltaTimePlayerLoopTimer(interval, periodic, playerLoopTiming, cancellationToken, timerCallback, state); } } public static PlayerLoopTimer StartNew(TimeSpan interval, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) { var timer = Create(interval, periodic, delayType, playerLoopTiming, cancellationToken, timerCallback, state); timer.Restart(); return timer; } /// /// Restart(Reset and Start) timer. /// public void Restart() { if (isDisposed) throw new ObjectDisposedException(null); ResetCore(null); // init state if (!isRunning) { isRunning = true; PlayerLoopHelper.AddAction(playerLoopTiming, this); } tryStop = false; } /// /// Restart(Reset and Start) and change interval. /// public void Restart(TimeSpan interval) { if (isDisposed) throw new ObjectDisposedException(null); ResetCore(interval); // init state if (!isRunning) { isRunning = true; PlayerLoopHelper.AddAction(playerLoopTiming, this); } tryStop = false; } /// /// Stop timer. /// public void Stop() { tryStop = true; } protected abstract void ResetCore(TimeSpan? newInterval); public void Dispose() { isDisposed = true; } bool IPlayerLoopItem.MoveNext() { if (isDisposed) { isRunning = false; return false; } if (tryStop) { isRunning = false; return false; } if (cancellationToken.IsCancellationRequested) { isRunning = false; return false; } if (!MoveNextCore()) { timerCallback(state); if (periodic) { ResetCore(null); return true; } else { isRunning = false; return false; } } return true; } protected abstract bool MoveNextCore(); } sealed class DeltaTimePlayerLoopTimer : PlayerLoopTimer { int initialFrame; float elapsed; float interval; public DeltaTimePlayerLoopTimer(TimeSpan interval, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) : base(periodic, playerLoopTiming, cancellationToken, timerCallback, state) { ResetCore(interval); } protected override bool MoveNextCore() { if (elapsed == 0.0f) { if (initialFrame == Time.frameCount) { return true; } } elapsed += Time.deltaTime; if (elapsed >= interval) { return false; } return true; } protected override void ResetCore(TimeSpan? interval) { this.elapsed = 0.0f; this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; if (interval != null) { this.interval = (float)interval.Value.TotalSeconds; } } } sealed class IgnoreTimeScalePlayerLoopTimer : PlayerLoopTimer { int initialFrame; float elapsed; float interval; public IgnoreTimeScalePlayerLoopTimer(TimeSpan interval, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) : base(periodic, playerLoopTiming, cancellationToken, timerCallback, state) { ResetCore(interval); } protected override bool MoveNextCore() { if (elapsed == 0.0f) { if (initialFrame == Time.frameCount) { return true; } } elapsed += Time.unscaledDeltaTime; if (elapsed >= interval) { return false; } return true; } protected override void ResetCore(TimeSpan? interval) { this.elapsed = 0.0f; this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; if (interval != null) { this.interval = (float)interval.Value.TotalSeconds; } } } sealed class RealtimePlayerLoopTimer : PlayerLoopTimer { ValueStopwatch stopwatch; long intervalTicks; public RealtimePlayerLoopTimer(TimeSpan interval, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action timerCallback, object state) : base(periodic, playerLoopTiming, cancellationToken, timerCallback, state) { ResetCore(interval); } protected override bool MoveNextCore() { if (stopwatch.ElapsedTicks >= intervalTicks) { return false; } return true; } protected override void ResetCore(TimeSpan? interval) { this.stopwatch = ValueStopwatch.StartNew(); if (interval != null) { this.intervalTicks = interval.Value.Ticks; } } } }