using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Health; /// /// Pure-logic merger for the per-host connectivity entries that /// surfaces. Holds the current set of host /// statuses (one synthetic top-level transport entry plus one entry per /// $WinPlatform/$AppEngine probe) and emits /// only when an upsert actually changes a host's /// — re-asserting the same state is a no-op so a stable /// ScanState=Running burst doesn't fan out duplicate transitions. /// /// /// This class owns the de-dup + diff logic that lived in /// GalaxyProxyDriver.OnHostConnectivityUpdate in v1. The watcher /// () and the transport forwarder /// () both feed this aggregator; the /// consumes from /// IHostConnectivityProbe.GetHostStatuses() and re-raises /// as the driver-level event (wired in PR 4.W). /// public sealed class HostStatusAggregator { private readonly object _lock = new(); private readonly Dictionary _byHost = new(StringComparer.OrdinalIgnoreCase); /// /// Fires when an call either introduces a new host or /// transitions an existing host's . Handlers run /// outside the internal lock so they can safely re-enter the aggregator /// (e.g. the driver re-broadcasting through IHostConnectivityProbe). /// public event EventHandler? OnHostStatusChanged; /// /// Snapshot the current host set. Suitable as the body of /// IHostConnectivityProbe.GetHostStatuses(). /// public IReadOnlyList Snapshot() { lock (_lock) { return [.. _byHost.Values]; } } /// /// Upsert the supplied status by . /// Raises when the host is newly tracked /// (previous state reported as ) or when its /// state value differs from the last cached entry. Re-asserting the same /// state is silent. /// public void Update(HostConnectivityStatus status) { ArgumentNullException.ThrowIfNull(status); HostState previous; bool changed; lock (_lock) { if (_byHost.TryGetValue(status.HostName, out var existing)) { previous = existing.State; changed = existing.State != status.State; } else { previous = HostState.Unknown; changed = true; } _byHost[status.HostName] = status; } if (changed) { OnHostStatusChanged?.Invoke(this, new HostStatusChangedEventArgs(status.HostName, previous, status.State)); } } /// /// Drop a host entirely (e.g. after a redeploy removes a Platform). No event /// is fired — observers only react to live transitions, not topology /// reductions. Returns true when the host was tracked. /// public bool Remove(string hostName) { ArgumentException.ThrowIfNullOrWhiteSpace(hostName); lock (_lock) { return _byHost.Remove(hostName); } } }