Example-Code / Utility / Timer.cs
Timer.cs
Raw
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");
            }
        }
    }
}