diff --git a/src/DtronixCommon/Threading/DelayedAction.cs b/src/DtronixCommon/Threading/DelayedAction.cs index e9ecd7f..9811617 100644 --- a/src/DtronixCommon/Threading/DelayedAction.cs +++ b/src/DtronixCommon/Threading/DelayedAction.cs @@ -1,4 +1,7 @@ -namespace DtronixCommon.Threading; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace DtronixCommon.Threading; /// /// Class to aid in the culling of events within a specified amount of time with a maximum delay. @@ -8,11 +11,15 @@ public class DelayedAction : IDisposable private readonly int _cullingInterval; private readonly int _maxCullingDelay; - protected Action Action; + private long _lastInvokedTick = 0; + private Action _action; private readonly Timer _timer; - private DateTime? _startTime; + private long? _startTick; private readonly SemaphoreSlim _semaphore = new(1, 1); + /// + /// True if an invocation is queued for execution. + /// public bool InvokeQueued { get; private set; } /// @@ -25,15 +32,52 @@ public DelayedAction(int cullingInterval, int maxCullingDelay, Action action) { _cullingInterval = cullingInterval; _maxCullingDelay = maxCullingDelay; - Action = action; + _action = action ?? throw new ArgumentNullException(nameof(action)); _timer = new Timer(TimerCallback, null, Timeout.Infinite, Timeout.Infinite); } - protected virtual void TimerCallback(object _) + /// + /// Method called when the timer time elapses. + /// + protected virtual void TimerCallback(object? _) { + // If the ticks are equal, the invocation was only called once and is ready to be fired now. + if (_lastInvokedTick == _startTick) + { + ExecuteCallback(); + return; + } + + var currentTicks = Environment.TickCount64; + var elapsedTime = currentTicks - _startTick!.Value; + + // See if we have exceeded the max culling delay. + if (elapsedTime >= _maxCullingDelay) + { + ExecuteCallback(); + return; + } + + // Check to see if we are exceeded the culling interval. + var lastInvokeTickDelta = currentTicks - _lastInvokedTick; + if (lastInvokeTickDelta >= _cullingInterval) + { + ExecuteCallback(); + } + } + + private void ExecuteCallback() + { + // Stop the timer. + _timer.Change(-1, -1); InvokeQueued = false; - _startTime = null; - Action?.Invoke(); + _startTick = null; + OnCallback(); + } + + protected virtual void OnCallback() + { + _action.Invoke(); } /// @@ -77,21 +121,21 @@ public void Invoke() private void InvokeInternal() { InvokeQueued = true; + _lastInvokedTick = Environment.TickCount64; // Check if we have exceeded the maximum delay time. - if (_maxCullingDelay == 0 - || (DateTime.UtcNow - (_startTime ??= DateTime.UtcNow)).TotalMilliseconds >= _maxCullingDelay) + if (_startTick == null) { - // Stop the timer. - _timer.Change(0, Timeout.Infinite); - return; + _startTick = _lastInvokedTick; + _timer.Change(_cullingInterval, _cullingInterval); } - - _timer.Change(_cullingInterval, Timeout.Infinite); + + } + public void Dispose() { - _timer?.Dispose(); - _semaphore?.Dispose(); + _timer.Dispose(); + _semaphore.Dispose(); } } \ No newline at end of file diff --git a/src/DtronixCommon/Threading/DelayedActionGeneric.cs b/src/DtronixCommon/Threading/DelayedActionGeneric.cs index 36af830..2788ab8 100644 --- a/src/DtronixCommon/Threading/DelayedActionGeneric.cs +++ b/src/DtronixCommon/Threading/DelayedActionGeneric.cs @@ -7,6 +7,7 @@ public class DelayedAction : DelayedAction where TArgs : struct { + private readonly Action _action; private TArgs _arguments; /// @@ -16,9 +17,14 @@ public class DelayedAction : DelayedAction /// Maximum delay that calls will be culled. /// Action to be invoked with the arguments passed. public DelayedAction(int cullingInterval, int maxCullingDelay, Action action) - : base(cullingInterval, maxCullingDelay, null) + : base(cullingInterval, maxCullingDelay, () => { }) { - Action = () => action(_arguments); + _action = action ?? throw new ArgumentNullException(nameof(action)); + } + + protected override void OnCallback() + { + _action.Invoke(_arguments); } ///