using System.Collections.Concurrent; using System.Threading.Channels; using Fleet.Core; using MoShiro.Protocol; namespace MoShiro.Backend.Application; /// 发布到 SSE 订阅者的信号。SSE 只负责“更快地获得变化”,推送的内容都能由 REST 查询重建。 public abstract record LiveSignal; /// 节点上下线等。 public sealed record NodeSignal(string Kind, string Id, bool Online, long AtMs) : LiveSignal; /// 某条序列有新的原始块入库。 public sealed record ChunkSignal(NodeId AgentId, string Series) : LiveSignal; public sealed record TestSignal(TestId TestId) : LiveSignal; /// 某个 Agent 的主机资源占用有了新的快照(实时推给展示页)。 public sealed record HostSignal(NodeId AgentId, HostSnapshot Host) : LiveSignal; public sealed class LiveSubscription : IDisposable { private readonly LiveHub _hub; private readonly Channel _channel = Channel.CreateBounded(new BoundedChannelOptions(256) { FullMode = BoundedChannelFullMode.DropOldest }); internal LiveSubscription(LiveHub hub) { _hub = hub; } public ChannelReader Reader => _channel.Reader; internal void Post(LiveSignal signal) => _channel.Writer.TryWrite(signal); public void Dispose() { _hub.Remove(this); _channel.Writer.TryComplete(); } } /// 内存中的发布订阅:入库、节点上下线、测试状态变化都经过这里。 public sealed class LiveHub { private readonly ConcurrentDictionary _subscriptions = new(); public int SubscriberCount => _subscriptions.Count; public LiveSubscription Subscribe() { var subscription = new LiveSubscription(this); _subscriptions[subscription] = 0; return subscription; } public void Publish(LiveSignal signal) { foreach (LiveSubscription subscription in _subscriptions.Keys) { subscription.Post(signal); } } internal void Remove(LiveSubscription subscription) => _subscriptions.TryRemove(subscription, out _); }