using Cysharp.Threading.Tasks; using System.Threading; using System; using CCG.Shared.Logging.Unity; using Microsoft.Extensions.Logging; #nullable enable namespace CCG.Bigfoot.Utility { /// <summary> /// Provides utility to start a timer and receive a callback when it is done or cancelled. /// </summary> /// <remarks> Callers new a Timer and call Run. Callers can cache a Timer and reuse it by calling Run without new. /// Authors: CS /// Created: 2024-01-05 /// </remarks> public sealed class Timer { public bool IsRunning { get { return _runningCancellation != null; } } private readonly TimeSpan _duration; private readonly Action? _onCompleted; private readonly Action? _onCancelled; private readonly CancellationToken _parentCancellationToken; private CancellationTokenSource? _runningCancellation; public bool IsDebugEnabled { get => _logger != null; set => _logger = (value ? Log.GetClassLogger<Timer>() : null); } private ILogger<Timer>? _logger; public Timer(int msDuration, Action? onCompleted = null, Action? onCancelled = null, CancellationToken cancellationToken = default) : this(TimeSpan.FromMilliseconds(msDuration), onCompleted, onCancelled, cancellationToken) { } public Timer(float secDuration, Action? onCompleted = null, Action? onCancelled = null, CancellationToken cancellationToken = default) : this(TimeSpan.FromSeconds(secDuration), onCompleted, onCancelled, cancellationToken) { } public Timer(TimeSpan duration, Action? onCompleted = null, Action? onCancelled = null, CancellationToken cancellationToken = default) { _duration = duration; _onCompleted = onCompleted; _onCancelled = onCancelled; _parentCancellationToken = cancellationToken; } public void Run() { if (_runningCancellation == null) { if (_parentCancellationToken.CanBeCanceled) { _runningCancellation = CancellationTokenSource.CreateLinkedTokenSource(_parentCancellationToken); } else { _runningCancellation = new(); } RunTimerAsync(_runningCancellation.Token).Forget(); } else if(IsDebugEnabled) { _logger!.LogWarning("Timer is already running. Create another Timer if you want another running in parallel."); } } public void Stop() { if (_runningCancellation != null) { _runningCancellation.Cancel(); } else if(IsDebugEnabled) { _logger!.LogWarning("Timer is not running. Can't Stop an inactive Timer."); } } async UniTaskVoid RunTimerAsync(CancellationToken ct) { try { await UniTask.Delay(_duration, cancellationToken: ct); _runningCancellation = null; _onCompleted?.Invoke(); } catch (OperationCanceledException) { if (IsDebugEnabled) _logger!.LogWarning("Timer was cancelled."); _onCancelled?.Invoke(); } finally { _runningCancellation?.Dispose(); _runningCancellation = null; if (IsDebugEnabled) _logger!.LogInformation("Timer's lifetime complete. Cancellation token was disposed and nulled"); } } } }