From b19ff742b66af77b17d57daade5fc32f686e90b6 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 22 Jun 2019 12:14:48 +0200 Subject: [PATCH 1/2] 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 From 5f203859ca00dadcef5efb870e8d42f97c1e7305 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 22 Jun 2019 12:21:48 +0200 Subject: [PATCH 2/2] Changed SteelSeriesDeviceUpdateTrigger to inherit DeviceUpdateTrigger --- .../Update/Devices/DeviceUpdateTrigger.cs | 40 +++---- .../Generic/SteelSeriesDeviceUpdateTrigger.cs | 104 ++---------------- 2 files changed, 29 insertions(+), 115 deletions(-) diff --git a/RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs b/RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs index 03410fb..04cfb92 100644 --- a/RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs +++ b/RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs @@ -53,12 +53,12 @@ namespace RGB.NET.Core } } - private AutoResetEvent _hasDataEvent = new AutoResetEvent(false); + protected AutoResetEvent HasDataEvent = new AutoResetEvent(false); - private bool _isRunning; - private Task _updateTask; - private CancellationTokenSource _updateTokenSource; - private CancellationToken _updateToken; + protected bool IsRunning; + protected Task UpdateTask; + protected CancellationTokenSource UpdateTokenSource; + protected CancellationToken UpdateToken; #endregion @@ -88,13 +88,13 @@ namespace RGB.NET.Core /// public void Start() { - if (_isRunning) return; + if (IsRunning) return; - _isRunning = true; + IsRunning = true; - _updateTokenSource?.Dispose(); - _updateTokenSource = new CancellationTokenSource(); - _updateTask = Task.Factory.StartNew(UpdateLoop, (_updateToken = _updateTokenSource.Token), TaskCreationOptions.LongRunning, TaskScheduler.Default); + UpdateTokenSource?.Dispose(); + UpdateTokenSource = new CancellationTokenSource(); + UpdateTask = Task.Factory.StartNew(UpdateLoop, (UpdateToken = UpdateTokenSource.Token), TaskCreationOptions.LongRunning, TaskScheduler.Default); } /// @@ -102,22 +102,22 @@ namespace RGB.NET.Core /// public async void Stop() { - if (!_isRunning) return; + if (!IsRunning) return; - _isRunning = false; + IsRunning = false; - _updateTokenSource.Cancel(); - await _updateTask; - _updateTask.Dispose(); - _updateTask = null; + UpdateTokenSource.Cancel(); + await UpdateTask; + UpdateTask.Dispose(); + UpdateTask = null; } - private void UpdateLoop() + protected virtual void UpdateLoop() { OnStartup(); - while (!_updateToken.IsCancellationRequested) + while (!UpdateToken.IsCancellationRequested) { - if (_hasDataEvent.WaitOne(Timeout)) + if (HasDataEvent.WaitOne(Timeout)) { long preUpdateTicks = Stopwatch.GetTimestamp(); @@ -135,7 +135,7 @@ namespace RGB.NET.Core } /// - public void TriggerHasData() => _hasDataEvent.Set(); + public void TriggerHasData() => HasDataEvent.Set(); private void UpdateUpdateFrequency() { diff --git a/RGB.NET.Devices.SteelSeries/Generic/SteelSeriesDeviceUpdateTrigger.cs b/RGB.NET.Devices.SteelSeries/Generic/SteelSeriesDeviceUpdateTrigger.cs index 2d5ac46..88c04ac 100644 --- a/RGB.NET.Devices.SteelSeries/Generic/SteelSeriesDeviceUpdateTrigger.cs +++ b/RGB.NET.Devices.SteelSeries/Generic/SteelSeriesDeviceUpdateTrigger.cs @@ -3,71 +3,24 @@ 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. + /// Represents an update-trigger used to update SteelSeries devices /// - public class SteelSeriesDeviceUpdateTrigger : AbstractUpdateTrigger, IDeviceUpdateTrigger + public class SteelSeriesDeviceUpdateTrigger : DeviceUpdateTrigger { #region Constants - private const long FLUSH_TIMER = 5 * 1000 * TimeSpan.TicksPerMillisecond; // every 5 seconds flush the device to prevent timeouts + private const long FLUSH_TIMER = 5 * 1000 * TimeSpan.TicksPerMillisecond; // flush the device every 5 seconds 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 @@ -76,6 +29,7 @@ namespace RGB.NET.Devices.SteelSeries /// /// Initializes a new instance of the class. /// + /// The hard limit of the update rate of this trigger. public SteelSeriesDeviceUpdateTrigger() { } @@ -84,49 +38,19 @@ namespace RGB.NET.Devices.SteelSeries /// /// The hard limit of the update rate of this trigger. public SteelSeriesDeviceUpdateTrigger(double updateRateHardLimit) - { - this.UpdateRateHardLimit = updateRateHardLimit; - } + : base(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() + protected override void UpdateLoop() { OnStartup(); - while (!_updateToken.IsCancellationRequested) + while (!UpdateToken.IsCancellationRequested) { - if (_hasDataEvent.WaitOne(Timeout)) + if (HasDataEvent.WaitOne(Timeout)) { long preUpdateTicks = Stopwatch.GetTimestamp(); @@ -146,16 +70,6 @@ namespace RGB.NET.Devices.SteelSeries } } - /// - public void TriggerHasData() => _hasDataEvent.Set(); - - private void UpdateUpdateFrequency() - { - UpdateFrequency = MaxUpdateRate; - if ((UpdateFrequency <= 0) || ((UpdateRateHardLimit > 0) && (UpdateRateHardLimit < UpdateFrequency))) - UpdateFrequency = UpdateRateHardLimit; - } - #endregion } }