1
0
mirror of https://github.com/DarthAffe/RGB.NET.git synced 2025-12-13 01:58:30 +00:00

Merge pull request #261 from DarthAffe/Core/Timers

Changed update-triggers to allow the usage of high resolution timers …
This commit is contained in:
DarthAffe 2022-05-07 16:10:37 +02:00 committed by GitHub
commit ead342388b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 188 additions and 41 deletions

View File

@ -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;
/// <summary>
/// Offers some helper methods for timed operations.
/// </summary>
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 bool _useHighResolutionTimers = true;
/// <summary>
/// Gets or sets if High Resolution Timers should be used.
/// </summary>
public static bool UseHighResolutionTimers
{
get => _useHighResolutionTimers;
set
{
lock (HIGH_RESOLUTION_TIMER_LOCK)
{
_useHighResolutionTimers = value;
CheckHighResolutionTimerUsage();
}
}
}
// ReSharper disable once InconsistentNaming
private static readonly HashSet<HighResolutionTimerDisposable> _timerLeases = new();
#endregion
#region Methods
/// <summary>
/// Executes the provided action and blocks if needed until the the <see param="targetExecuteTime"/> has passed.
/// </summary>
/// <param name="action">The action to execute.</param>
/// <param name="targetExecuteTime">The time in ms this method should block. default: 0</param>
/// <returns>The time in ms spent executing the <see param="action"/>.</returns>
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;
}
/// <summary>
/// Calculates the elapsed time in ms from the provided timestamp until now.
/// </summary>
/// <param name="initialTimestamp">The initial timestamp to calculate the time from.</param>
/// <returns>The elapsed time in ms.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double GetElapsedTime(long initialTimestamp) => ((Stopwatch.GetTimestamp() - initialTimestamp) / (double)TimeSpan.TicksPerMillisecond);
/// <summary>
/// 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.
/// </summary>
/// <returns>A disposable to remove the request.</returns>
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;
}
}
/// <summary>
/// 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.
/// </summary>
public static void DisposeAllHighResolutionTimerRequests()
{
List<HighResolutionTimerDisposable> timerLeases = new(_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
}
}

View File

@ -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);
}
/// <inheritdoc />

View File

@ -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));
}
}

View File

@ -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);
}
}
}
/// <inheritdoc />

View File

@ -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 },