// ReSharper disable MemberCanBePrivate.Global using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace RGB.NET.Core; /// /// Represents an update-trigger used to update devices with a maximum update-rate. /// public class DeviceUpdateTrigger : AbstractUpdateTrigger, IDeviceUpdateTrigger { #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(); } } /// /// Gets or sets the time in ms after which a refresh-request is sent even if no changes are made in the meantime to prevent the target from timing out or similar problems. /// To disable heartbeats leave it at 0. /// public int HeartbeatTimer { get; set; } /// public override double LastUpdateTime { get; protected set; } /// /// Gets or sets the timestamp of the last update. /// protected long LastUpdateTimestamp { get; set; } /// /// Gets or sets the event to trigger when new data is available (). /// protected AutoResetEvent HasDataEvent { get; set; } = new(false); /// /// Gets or sets a bool indicating if the trigger is currently updating. /// protected bool IsRunning { get; set; } /// /// Gets or sets the update loop of this trigger. /// protected Task? UpdateTask { get; set; } /// /// Gets or sets the cancellation token source used to create the cancellation token checked by the . /// protected CancellationTokenSource? UpdateTokenSource { get; set; } /// /// Gets or sets the cancellation token checked by the . /// protected CancellationToken UpdateToken { get; set; } #endregion #region Constructors /// /// Initializes a new instance of the class. /// public DeviceUpdateTrigger() { } /// /// Initializes a new instance of the class. /// /// The hard limit of the update rate of this trigger. public DeviceUpdateTrigger(double updateRateHardLimit) { this.UpdateRateHardLimit = updateRateHardLimit; } #endregion #region Methods /// /// Starts the trigger. /// public override 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 virtual async void Stop() { if (!IsRunning) return; IsRunning = false; UpdateTokenSource?.Cancel(); if (UpdateTask != null) try { await UpdateTask.ConfigureAwait(false); } catch (TaskCanceledException) { } catch (OperationCanceledException) { } UpdateTask?.Dispose(); UpdateTask = null; } /// /// The update loop called by the . /// protected virtual void UpdateLoop() { OnStartup(); using (TimerHelper.RequestHighResolutionTimer()) while (!UpdateToken.IsCancellationRequested) if (HasDataEvent.WaitOne(Timeout)) LastUpdateTime = TimerHelper.Execute(TimerExecute, UpdateFrequency * 1000); else if ((HeartbeatTimer > 0) && (LastUpdateTimestamp > 0) && (TimerHelper.GetElapsedTime(LastUpdateTimestamp) > HeartbeatTimer)) OnUpdate(new CustomUpdateData().Heartbeat()); } private void TimerExecute() => OnUpdate(); protected override void OnUpdate(CustomUpdateData? updateData = null) { base.OnUpdate(updateData); LastUpdateTimestamp = Stopwatch.GetTimestamp(); } /// public void TriggerHasData() => HasDataEvent.Set(); private void UpdateUpdateFrequency() { UpdateFrequency = MaxUpdateRate; if ((UpdateFrequency <= 0) || ((UpdateRateHardLimit > 0) && (UpdateRateHardLimit < UpdateFrequency))) UpdateFrequency = UpdateRateHardLimit; } /// public override void Dispose() { Stop(); GC.SuppressFinalize(this); } #endregion }