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 _);
}