perf(ui): shared KPI cache, live-cache-backed alarm summary, coalesced debug renders
This commit is contained in:
@@ -236,13 +236,66 @@
|
||||
DeploymentStatusNotifier.StatusChanged += OnDeploymentStatusChanged;
|
||||
}
|
||||
|
||||
// ── Push coalescing (arch-review WP2.4) ───────────────────────────────────
|
||||
// One reload = every deployment record + every instance, re-read and re-filtered.
|
||||
// The notifier fires per status WRITE, so a site deploy of N instances produced
|
||||
// 2N+ of those full reloads back to back, per circuit. The reload is now
|
||||
// leading-edge debounced: the first push after an idle gap still reloads
|
||||
// immediately (a single deployment stays as responsive as before), and every
|
||||
// push inside the window is absorbed into ONE trailing reload.
|
||||
private const int ReloadDebounceMs = 500;
|
||||
|
||||
private DateTimeOffset _lastReloadAt = DateTimeOffset.MinValue;
|
||||
|
||||
/// <summary>Coalescing timer for pushes arriving inside the debounce window. Disposed with the component.</summary>
|
||||
private Timer? _coalesceTimer;
|
||||
|
||||
private void OnDeploymentStatusChanged(ZB.MOM.WW.ScadaBridge.DeploymentManager.DeploymentStatusChange change)
|
||||
{
|
||||
// CentralUI-022: a callback racing disposal must not touch the component.
|
||||
if (_disposed || !_autoRefresh) return;
|
||||
|
||||
lock (_coalesceLock)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
var sinceLast = DateTimeOffset.UtcNow - _lastReloadAt;
|
||||
if (sinceLast >= TimeSpan.FromMilliseconds(ReloadDebounceMs))
|
||||
{
|
||||
// Idle — reload straight away (leading edge).
|
||||
_lastReloadAt = DateTimeOffset.UtcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inside the window: arm one trailing reload for the remainder, and
|
||||
// let any further push in this window ride it.
|
||||
if (_coalesceTimer is not null) return;
|
||||
|
||||
var delay = TimeSpan.FromMilliseconds(ReloadDebounceMs) - sinceLast;
|
||||
_coalesceTimer = new Timer(_ => OnCoalesceElapsed(), null, delay, Timeout.InfiniteTimeSpan);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_ = DispatchReloadAsync();
|
||||
}
|
||||
|
||||
/// <summary>Trailing edge of the debounce: disarm, stamp, and run the one coalesced reload.</summary>
|
||||
private void OnCoalesceElapsed()
|
||||
{
|
||||
lock (_coalesceLock)
|
||||
{
|
||||
_coalesceTimer?.Dispose();
|
||||
_coalesceTimer = null;
|
||||
if (_disposed) return;
|
||||
_lastReloadAt = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
_ = DispatchReloadAsync();
|
||||
}
|
||||
|
||||
private readonly object _coalesceLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the deployment table on the renderer's dispatcher, guarded
|
||||
/// against the component being disposed mid-flight (CentralUI-022):
|
||||
@@ -361,5 +414,10 @@
|
||||
// status change reaches this disposed component.
|
||||
_disposed = true;
|
||||
DeploymentStatusNotifier.StatusChanged -= OnDeploymentStatusChanged;
|
||||
lock (_coalesceLock)
|
||||
{
|
||||
_coalesceTimer?.Dispose();
|
||||
_coalesceTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user