From b19ff742b66af77b17d57daade5fc32f686e90b6 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 22 Jun 2019 12:14:48 +0200 Subject: [PATCH] Added refresh logic to prevent the SDK from timing out --- .../Generic/SteelSeriesDeviceUpdateQueue.cs | 13 ++ .../Generic/SteelSeriesDeviceUpdateTrigger.cs | 161 ++++++++++++++++++ .../SteelSeriesDeviceProvider.cs | 6 +- 3 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 RGB.NET.Devices.SteelSeries/Generic/SteelSeriesDeviceUpdateTrigger.cs diff --git a/RGB.NET.Devices.SteelSeries/Generic/SteelSeriesDeviceUpdateQueue.cs b/RGB.NET.Devices.SteelSeries/Generic/SteelSeriesDeviceUpdateQueue.cs index 9b43d5c..fe3d576 100644 --- a/RGB.NET.Devices.SteelSeries/Generic/SteelSeriesDeviceUpdateQueue.cs +++ b/RGB.NET.Devices.SteelSeries/Generic/SteelSeriesDeviceUpdateQueue.cs @@ -15,6 +15,7 @@ namespace RGB.NET.Devices.SteelSeries #region Properties & Fields private string _deviceType; + private Dictionary _lastDataSet; #endregion @@ -35,9 +36,21 @@ namespace RGB.NET.Devices.SteelSeries #region Methods + protected override void OnUpdate(object sender, CustomUpdateData customData) + { + if ((customData != null) && (customData["refresh"] as bool? ?? false)) + { + if ((_lastDataSet != null) && (_lastDataSet.Count != 0)) + Update(_lastDataSet); + } + else + base.OnUpdate(sender, customData); + } + /// protected override void Update(Dictionary dataSet) { + _lastDataSet = dataSet; SteelSeriesSDK.UpdateLeds(_deviceType, dataSet.ToDictionary(x => ((SteelSeriesLedId)x.Key).GetAPIName(), x => x.Value.ToIntArray())); } diff --git a/RGB.NET.Devices.SteelSeries/Generic/SteelSeriesDeviceUpdateTrigger.cs b/RGB.NET.Devices.SteelSeries/Generic/SteelSeriesDeviceUpdateTrigger.cs new file mode 100644 index 0000000..2d5ac46 --- /dev/null +++ b/RGB.NET.Devices.SteelSeries/Generic/SteelSeriesDeviceUpdateTrigger.cs @@ -0,0 +1,161 @@ +// ReSharper disable MemberCanBePrivate.Global + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using RGB.NET.Core; + +namespace RGB.NET.Devices.SteelSeries +{ + /// + /// Represents an update-trigger used to update devices with a maximum update-rate. + /// + public class SteelSeriesDeviceUpdateTrigger : AbstractUpdateTrigger, IDeviceUpdateTrigger + { + #region Constants + + private const long FLUSH_TIMER = 5 * 1000 * TimeSpan.TicksPerMillisecond; // every 5 seconds flush the device to prevent timeouts + + #endregion + + #region Properties & Fields + + /// + /// Gets or sets the timeout used by the blocking wait for data availability. + /// + public int Timeout { get; set; } = 100; + + /// + /// Gets the update frequency used by the trigger if not limited by data shortage. + /// + public double UpdateFrequency { get; private set; } + + private double _maxUpdateRate; + /// + /// Gets or sets the maximum update rate of this trigger (is overwriten if the is smaller). + /// <= 0 removes the limit. + /// + public double MaxUpdateRate + { + get => _maxUpdateRate; + set + { + _maxUpdateRate = value; + UpdateUpdateFrequency(); + } + } + + private double _updateRateHardLimit; + /// + /// Gets the hard limit of the update rate of this trigger. Updates will never perform faster then then this value if it's set. + /// <= 0 removes the limit. + /// + public double UpdateRateHardLimit + { + get => _updateRateHardLimit; + protected set + { + _updateRateHardLimit = value; + UpdateUpdateFrequency(); + } + } + + private AutoResetEvent _hasDataEvent = new AutoResetEvent(false); + + private long _lastUpdateTimestamp; + private bool _isRunning; + private Task _updateTask; + private CancellationTokenSource _updateTokenSource; + private CancellationToken _updateToken; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public SteelSeriesDeviceUpdateTrigger() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The hard limit of the update rate of this trigger. + public SteelSeriesDeviceUpdateTrigger(double updateRateHardLimit) + { + this.UpdateRateHardLimit = updateRateHardLimit; + } + + #endregion + + #region Methods + + /// + /// Starts the trigger. + /// + public void Start() + { + if (_isRunning) return; + + _isRunning = true; + + _updateTokenSource?.Dispose(); + _updateTokenSource = new CancellationTokenSource(); + _updateTask = Task.Factory.StartNew(UpdateLoop, (_updateToken = _updateTokenSource.Token), TaskCreationOptions.LongRunning, TaskScheduler.Default); + } + + /// + /// Stops the trigger. + /// + public async void Stop() + { + if (!_isRunning) return; + + _isRunning = false; + + _updateTokenSource.Cancel(); + await _updateTask; + _updateTask.Dispose(); + _updateTask = null; + } + + private void UpdateLoop() + { + OnStartup(); + while (!_updateToken.IsCancellationRequested) + { + if (_hasDataEvent.WaitOne(Timeout)) + { + long preUpdateTicks = Stopwatch.GetTimestamp(); + + OnUpdate(); + + if (UpdateFrequency > 0) + { + _lastUpdateTimestamp = Stopwatch.GetTimestamp(); + double lastUpdateTime = ((_lastUpdateTimestamp - preUpdateTicks) / (double)TimeSpan.TicksPerMillisecond); + int sleep = (int)((UpdateFrequency * 1000.0) - lastUpdateTime); + if (sleep > 0) + Thread.Sleep(sleep); + } + } + else if (((Stopwatch.GetTimestamp() - _lastUpdateTimestamp) > FLUSH_TIMER)) + OnUpdate(new CustomUpdateData(("refresh", true))); + } + } + + /// + public void TriggerHasData() => _hasDataEvent.Set(); + + private void UpdateUpdateFrequency() + { + UpdateFrequency = MaxUpdateRate; + if ((UpdateFrequency <= 0) || ((UpdateRateHardLimit > 0) && (UpdateRateHardLimit < UpdateFrequency))) + UpdateFrequency = UpdateRateHardLimit; + } + + #endregion + } +} diff --git a/RGB.NET.Devices.SteelSeries/SteelSeriesDeviceProvider.cs b/RGB.NET.Devices.SteelSeries/SteelSeriesDeviceProvider.cs index 432c51b..9f59d7d 100644 --- a/RGB.NET.Devices.SteelSeries/SteelSeriesDeviceProvider.cs +++ b/RGB.NET.Devices.SteelSeries/SteelSeriesDeviceProvider.cs @@ -37,9 +37,9 @@ namespace RGB.NET.Devices.SteelSeries public IEnumerable Devices { get; private set; } /// - /// The used to trigger the updates for SteelSeries devices. + /// The used to trigger the updates for SteelSeries devices. /// - public DeviceUpdateTrigger UpdateTrigger { get; } + public SteelSeriesDeviceUpdateTrigger UpdateTrigger { get; } #endregion @@ -54,7 +54,7 @@ namespace RGB.NET.Devices.SteelSeries if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(SteelSeriesDeviceProvider)}"); _instance = this; - UpdateTrigger = new DeviceUpdateTrigger(); + UpdateTrigger = new SteelSeriesDeviceUpdateTrigger(); } #endregion