namespace ChessCubing.Server.Social; public sealed class ConnectedUserTracker { private readonly object _sync = new(); private readonly Dictionary _subjectByConnection = new(StringComparer.Ordinal); private readonly Dictionary> _connectionsBySubject = new(StringComparer.Ordinal); public bool TrackConnection(string connectionId, string subject) { lock (_sync) { _subjectByConnection[connectionId] = subject; if (!_connectionsBySubject.TryGetValue(subject, out var connections)) { connections = new HashSet(StringComparer.Ordinal); _connectionsBySubject[subject] = connections; } var wasOffline = connections.Count == 0; connections.Add(connectionId); return wasOffline; } } public (string? Subject, bool BecameOffline) RemoveConnection(string connectionId) { lock (_sync) { if (!_subjectByConnection.Remove(connectionId, out var subject)) { return (null, false); } if (!_connectionsBySubject.TryGetValue(subject, out var connections)) { return (subject, false); } connections.Remove(connectionId); if (connections.Count > 0) { return (subject, false); } _connectionsBySubject.Remove(subject); return (subject, true); } } public bool IsOnline(string subject) { lock (_sync) { return _connectionsBySubject.TryGetValue(subject, out var connections) && connections.Count > 0; } } }