// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace LibreHardwareMonitor.Windows.WinUI.Services;
///
/// Coalesces a burst of hardware-change notifications into as few tree rebuilds as possible without dropping the last
/// one. After the first request a single worker waits a short debounce delay, then rebuilds while more changes keep
/// arriving; a change that lands between the worker finishing and the queued flag clearing re-arms the worker.
/// Extracted from so this concurrency logic can be unit-tested with a controllable
/// delay.
///
internal sealed class TreeRebuildCoalescer
{
private readonly Action _rebuild;
private readonly Func _canRebuild;
private readonly Func _delay;
private int _queued;
private volatile bool _dirty;
public TreeRebuildCoalescer(Action rebuild, Func canRebuild, Func? delay = null)
{
_rebuild = rebuild;
_canRebuild = canRebuild;
_delay = delay ?? (() => Task.Delay(100));
}
/// The most recently started worker. Exposed only so tests can deterministically await a rebuild cycle.
internal Task RebuildLoopTask { get; private set; } = Task.CompletedTask;
public void Request()
{
_dirty = true;
if (Interlocked.Exchange(ref _queued, 1) == 1)
return;
RebuildLoopTask = Task.Run(async () =>
{
try
{
await _delay().ConfigureAwait(false);
// Rebuild until no change has arrived since the last rebuild began, so a change that lands while a
// rebuild is in flight is not coalesced away and lost.
while (_dirty)
{
_dirty = false;
if (_canRebuild())
_rebuild();
}
}
catch (Exception ex)
{
Debug.WriteLine($"Hardware tree rebuild failed: {ex}");
}
finally
{
Interlocked.Exchange(ref _queued, 0);
// A change may have slipped in between the loop's exit and clearing the flag; re-queue so it is honored.
if (_dirty)
Request();
}
});
}
}