From f02d4564fa04b9816b22119093350a2f2ec94e9d Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 7 May 2022 00:18:52 +0200 Subject: [PATCH 1/5] Changed update-triggers to allow the usage of high resolution timers to improve accuracy (enabled by default) --- RGB.NET.Core/Helper/TimerHelper.cs | 179 ++++++++++++++++++ .../Update/Devices/DeviceUpdateTrigger.cs | 24 +-- RGB.NET.Core/Update/ManualUpdateTrigger.cs | 7 +- RGB.NET.Core/Update/TimerUpdateTrigger.cs | 18 +- 4 files changed, 187 insertions(+), 41 deletions(-) create mode 100644 RGB.NET.Core/Helper/TimerHelper.cs diff --git a/RGB.NET.Core/Helper/TimerHelper.cs b/RGB.NET.Core/Helper/TimerHelper.cs new file mode 100644 index 0000000..fac9476 --- /dev/null +++ b/RGB.NET.Core/Helper/TimerHelper.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace RGB.NET.Core; + +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 object HIGH_RESOLUTION_TIMER_LOCK = new(); + + private static bool _areHighResolutionTimersEnabled = false; + private static int _highResolutionTimerUsers = 0; + + 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 = new(); + + #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 ellapsed 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) / (double)TimeSpan.TicksPerMillisecond); + + /// + /// 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() + { + lock (HIGH_RESOLUTION_TIMER_LOCK) + { + _highResolutionTimerUsers++; + CheckHighResolutionTimerUsage(); + } + + HighResolutionTimerDisposable timerLease = new(); + _timerLeases.Add(timerLease); + return timerLease; + } + + private static void CheckHighResolutionTimerUsage() + { + if (UseHighResolutionTimers && (_highResolutionTimerUsers > 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.ToList(); + 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); + _highResolutionTimerUsers--; + CheckHighResolutionTimerUsage(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs b/RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs index d2ce849..6868a87 100644 --- a/RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs +++ b/RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs @@ -1,6 +1,5 @@ // ReSharper disable MemberCanBePrivate.Global -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -142,25 +141,10 @@ public class DeviceUpdateTrigger : AbstractUpdateTrigger, IDeviceUpdateTrigger { OnStartup(); - while (!UpdateToken.IsCancellationRequested) - { - if (HasDataEvent.WaitOne(Timeout)) - { - long preUpdateTicks = Stopwatch.GetTimestamp(); - - OnUpdate(); - - double lastUpdateTime = ((Stopwatch.GetTimestamp() - preUpdateTicks) / 10000.0); - LastUpdateTime = lastUpdateTime; - - if (UpdateFrequency > 0) - { - int sleep = (int)((UpdateFrequency * 1000.0) - lastUpdateTime); - if (sleep > 0) - Thread.Sleep(sleep); - } - } - } + using (TimerHelper.RequestHighResolutionTimer()) + while (!UpdateToken.IsCancellationRequested) + if (HasDataEvent.WaitOne(Timeout)) + LastUpdateTime = TimerHelper.Execute(() => OnUpdate(), UpdateFrequency * 1000); } /// diff --git a/RGB.NET.Core/Update/ManualUpdateTrigger.cs b/RGB.NET.Core/Update/ManualUpdateTrigger.cs index f9b315c..67e8ca4 100644 --- a/RGB.NET.Core/Update/ManualUpdateTrigger.cs +++ b/RGB.NET.Core/Update/ManualUpdateTrigger.cs @@ -1,6 +1,5 @@ // ReSharper disable MemberCanBePrivate.Global -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -86,11 +85,7 @@ public sealed class ManualUpdateTrigger : AbstractUpdateTrigger while (!UpdateToken.IsCancellationRequested) { if (_mutex.WaitOne(100)) - { - long preUpdateTicks = Stopwatch.GetTimestamp(); - OnUpdate(_customUpdateData); - LastUpdateTime = ((Stopwatch.GetTimestamp() - preUpdateTicks) / 10000.0); - } + LastUpdateTime = TimerHelper.Execute(() => OnUpdate(_customUpdateData)); } } diff --git a/RGB.NET.Core/Update/TimerUpdateTrigger.cs b/RGB.NET.Core/Update/TimerUpdateTrigger.cs index d15f049..14a7e9f 100644 --- a/RGB.NET.Core/Update/TimerUpdateTrigger.cs +++ b/RGB.NET.Core/Update/TimerUpdateTrigger.cs @@ -1,7 +1,6 @@ // ReSharper disable MemberCanBePrivate.Global using System; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -130,21 +129,10 @@ public class TimerUpdateTrigger : AbstractUpdateTrigger { OnStartup(); - while (!UpdateToken.IsCancellationRequested) - { - long preUpdateTicks = Stopwatch.GetTimestamp(); + using (TimerHelper.RequestHighResolutionTimer()) + while (!UpdateToken.IsCancellationRequested) + LastUpdateTime = TimerHelper.Execute(() => OnUpdate(_customUpdateData), UpdateFrequency * 1000); - OnUpdate(_customUpdateData); - - if (UpdateFrequency > 0) - { - double lastUpdateTime = ((Stopwatch.GetTimestamp() - preUpdateTicks) / 10000.0); - LastUpdateTime = lastUpdateTime; - int sleep = (int)((UpdateFrequency * 1000.0) - lastUpdateTime); - if (sleep > 0) - Thread.Sleep(sleep); - } - } } /// From 39b511b8c5c3d0d24d93277568289740568c5541 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 7 May 2022 00:20:12 +0200 Subject: [PATCH 2/5] Added missing class-doc --- RGB.NET.Core/Helper/TimerHelper.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RGB.NET.Core/Helper/TimerHelper.cs b/RGB.NET.Core/Helper/TimerHelper.cs index fac9476..9958201 100644 --- a/RGB.NET.Core/Helper/TimerHelper.cs +++ b/RGB.NET.Core/Helper/TimerHelper.cs @@ -8,6 +8,9 @@ using System.Threading; namespace RGB.NET.Core; +/// +/// Offers some helper methods for timed operations. +/// public static class TimerHelper { #region DLL-Imports From 02d6f4e53e9782d0aa9f27ef4d3741a1a36f21bd Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 7 May 2022 15:55:54 +0200 Subject: [PATCH 3/5] Fixed typo --- RGB.NET.Core/Helper/TimerHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RGB.NET.Core/Helper/TimerHelper.cs b/RGB.NET.Core/Helper/TimerHelper.cs index 9958201..6e54cc5 100644 --- a/RGB.NET.Core/Helper/TimerHelper.cs +++ b/RGB.NET.Core/Helper/TimerHelper.cs @@ -79,7 +79,7 @@ public static class TimerHelper } /// - /// Calculates the ellapsed time in ms from the provided timestamp until now. + /// 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. From d555406722326e28a6b2732f78330b78657f2423 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 7 May 2022 16:00:23 +0200 Subject: [PATCH 4/5] Removed high resolution timer counter --- RGB.NET.Core/Helper/TimerHelper.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/RGB.NET.Core/Helper/TimerHelper.cs b/RGB.NET.Core/Helper/TimerHelper.cs index 6e54cc5..6c33284 100644 --- a/RGB.NET.Core/Helper/TimerHelper.cs +++ b/RGB.NET.Core/Helper/TimerHelper.cs @@ -28,7 +28,6 @@ public static class TimerHelper private static readonly object HIGH_RESOLUTION_TIMER_LOCK = new(); private static bool _areHighResolutionTimersEnabled = false; - private static int _highResolutionTimerUsers = 0; private static bool _useHighResolutionTimers = true; /// @@ -93,20 +92,19 @@ public static class TimerHelper /// A disposable to remove the request. public static IDisposable RequestHighResolutionTimer() { + HighResolutionTimerDisposable timerLease = new(); lock (HIGH_RESOLUTION_TIMER_LOCK) { - _highResolutionTimerUsers++; + _timerLeases.Add(timerLease); CheckHighResolutionTimerUsage(); } - HighResolutionTimerDisposable timerLease = new(); - _timerLeases.Add(timerLease); return timerLease; } private static void CheckHighResolutionTimerUsage() { - if (UseHighResolutionTimers && (_highResolutionTimerUsers > 0)) + if (UseHighResolutionTimers && (_timerLeases.Count > 0)) EnableHighResolutionTimers(); else DisableHighResolutionTimers(); @@ -146,7 +144,7 @@ public static class TimerHelper /// public static void DisposeAllHighResolutionTimerRequests() { - List timerLeases = _timerLeases.ToList(); + List timerLeases = new(_timerLeases); foreach (HighResolutionTimerDisposable timer in timerLeases) timer.Dispose(); } @@ -172,7 +170,6 @@ public static class TimerHelper lock (HIGH_RESOLUTION_TIMER_LOCK) { _timerLeases.Remove(this); - _highResolutionTimerUsers--; CheckHighResolutionTimerUsage(); } } From d1dae22133e9fb8a65a7eb12e9daafd9b4a06626 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 7 May 2022 16:09:43 +0200 Subject: [PATCH 5/5] Added Razer Basilisk v3 PID --- RGB.NET.Devices.Razer/RazerDeviceProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/RGB.NET.Devices.Razer/RazerDeviceProvider.cs b/RGB.NET.Devices.Razer/RazerDeviceProvider.cs index 2e1292f..efcb7b0 100644 --- a/RGB.NET.Devices.Razer/RazerDeviceProvider.cs +++ b/RGB.NET.Devices.Razer/RazerDeviceProvider.cs @@ -165,6 +165,7 @@ public class RazerDeviceProvider : AbstractRGBDeviceProvider { 0x008D, RGBDeviceType.Mouse, "Naga Left Handed Edition", LedMappings.Mouse, RazerEndpointType.Mouse }, { 0x0091, RGBDeviceType.Mouse, "Viper 8khz", LedMappings.Mouse, RazerEndpointType.Mouse }, { 0x0096, RGBDeviceType.Mouse, "Naga X", LedMappings.Mouse, RazerEndpointType.Mouse }, + { 0x0099, RGBDeviceType.Mouse, "Basilisk v3", LedMappings.Mouse, RazerEndpointType.Mouse }, // Mousepads { 0x0068, RGBDeviceType.Mousepad, "Firefly Hyperflux", LedMappings.Mousepad, RazerEndpointType.Mousepad },