using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace GameCult.Networking { /// /// Presents one logical CultNet schema server over several physical transports. /// It owns handler fan-out and peer-lifecycle aggregation, but never owns or /// disposes the transport servers themselves. /// public sealed class CultNetSchemaServerGroup : ICultNetSchemaServer, ICultNetSchemaServerPeerLifecycle, IDisposable { private readonly IReadOnlyList _servers; private readonly IReadOnlyList _lifecycles; private bool _disposed; /// Creates one logical server from two or more transport servers. public CultNetSchemaServerGroup(params ICultNetSchemaServer[] servers) { if (servers == null) throw new ArgumentNullException(nameof(servers)); if (servers.Length < 2) throw new ArgumentException("A CultNet schema server group requires at least two transports.", nameof(servers)); if (servers.Any(server => server == null)) throw new ArgumentException("A CultNet schema server group cannot contain a null transport.", nameof(servers)); for (var left = 0; left < servers.Length; left++) for (var right = left + 1; right < servers.Length; right++) { if (ReferenceEquals(servers[left], servers[right])) throw new ArgumentException("A CultNet schema server group cannot contain the same transport twice.", nameof(servers)); } _servers = Array.AsReadOnly(servers.ToArray()); _lifecycles = Array.AsReadOnly(servers.OfType().ToArray()); foreach (var lifecycle in _lifecycles) lifecycle.PeerDisconnected += ForwardPeerDisconnected; } /// public event Action? PeerDisconnected; /// public void OnCultNet(Func callback) where TMessage : ICultNetSchemaMessage { if (callback == null) throw new ArgumentNullException(nameof(callback)); ThrowIfDisposed(); var registered = 0; try { foreach (var server in _servers) { server.OnCultNet(callback); registered++; } } catch { for (var index = 0; index < registered; index++) _servers[index].RemoveCultNetMessageListener(callback); throw; } } /// public void RemoveCultNetMessageListener(Delegate callback) where TMessage : ICultNetSchemaMessage { if (callback == null) throw new ArgumentNullException(nameof(callback)); if (_disposed) return; foreach (var server in _servers) server.RemoveCultNetMessageListener(callback); } /// /// Detaches lifecycle forwarding. The physical transports retain their /// independent ownership and must be disposed by their creator. /// public void Dispose() { if (_disposed) return; _disposed = true; foreach (var lifecycle in _lifecycles) lifecycle.PeerDisconnected -= ForwardPeerDisconnected; PeerDisconnected = null; } private void ForwardPeerDisconnected(ICultNetSchemaServerPeer peer) => PeerDisconnected?.Invoke(peer); private void ThrowIfDisposed() { if (_disposed) throw new ObjectDisposedException(nameof(CultNetSchemaServerGroup)); } } }