using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; namespace RGB.NET.Core; /// /// Offers some helper methods for timed operations. /// public static class TimerHelper { #region DLL-Imports [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")] private static extern void TimeBeginPeriod(int t); [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")] private static extern void TimeEndPeriod(int t); #endregion #region Properties & Fields private static readonly Lock HIGH_RESOLUTION_TIMER_LOCK = new(); private static bool _areHighResolutionTimersEnabled = false; private static bool _useHighResolutionTimers = true; /// /// Gets or sets if High Resolution Timers should be used. /// public static bool UseHighResolutionTimers { get => _useHighResolutionTimers; set { lock (HIGH_RESOLUTION_TIMER_LOCK) { _useHighResolutionTimers = value; CheckHighResolutionTimerUsage(); } } } // ReSharper disable once InconsistentNaming private static readonly HashSet _timerLeases = []; #endregion #region Methods /// /// Executes the provided action and blocks if needed until the the has passed. /// /// The action to execute. /// The time in ms this method should block. default: 0 /// The time in ms spent executing the . public static double Execute(Action action, double targetExecuteTime = 0) { long preUpdateTicks = Stopwatch.GetTimestamp(); action(); double updateTime = GetElapsedTime(preUpdateTicks); if (targetExecuteTime > 0) { int sleep = (int)(targetExecuteTime - updateTime); if (sleep > 0) Thread.Sleep(sleep); } return updateTime; } /// /// Calculates the elapsed time in ms from the provided timestamp until now. /// /// The initial timestamp to calculate the time from. /// The elapsed time in ms. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double GetElapsedTime(long initialTimestamp) => ((Stopwatch.GetTimestamp() - initialTimestamp) / (Stopwatch.Frequency / 1000.0)); /// /// Requests to use to use High Resolution Timers if enabled. /// IMPORTANT: Always dispose the returned disposable if High Resolution Timers are no longer needed for the caller. /// /// A disposable to remove the request. public static IDisposable RequestHighResolutionTimer() { HighResolutionTimerDisposable timerLease = new(); lock (HIGH_RESOLUTION_TIMER_LOCK) { _timerLeases.Add(timerLease); CheckHighResolutionTimerUsage(); } return timerLease; } private static void CheckHighResolutionTimerUsage() { if (UseHighResolutionTimers && (_timerLeases.Count > 0)) EnableHighResolutionTimers(); else DisableHighResolutionTimers(); } private static void EnableHighResolutionTimers() { lock (HIGH_RESOLUTION_TIMER_LOCK) { if (_areHighResolutionTimersEnabled) return; // DarthAffe 06.05.2022: Linux should use 1ms timers by default if (OperatingSystem.IsWindows()) TimeBeginPeriod(1); _areHighResolutionTimersEnabled = true; } } private static void DisableHighResolutionTimers() { lock (HIGH_RESOLUTION_TIMER_LOCK) { if (!_areHighResolutionTimersEnabled) return; if (OperatingSystem.IsWindows()) TimeEndPeriod(1); _areHighResolutionTimersEnabled = false; } } /// /// Disposes all open High Resolution Timer Requests. /// This should be called once when exiting the application to make sure nothing remains open and the application correctly unregisters itself on OS level. /// Shouldn't be needed if everything is disposed, but better safe then sorry. /// public static void DisposeAllHighResolutionTimerRequests() { List timerLeases = [.._timerLeases]; foreach (HighResolutionTimerDisposable timer in timerLeases) timer.Dispose(); } #endregion private class HighResolutionTimerDisposable : IDisposable { #region Properties & Fields private bool _isDisposed = false; #endregion #region Methods public void Dispose() { if (_isDisposed) return; _isDisposed = true; lock (HIGH_RESOLUTION_TIMER_LOCK) { _timerLeases.Remove(this); CheckHighResolutionTimerUsage(); } } #endregion } }