using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Syntriax.Engine.Core; namespace Syntriax.Engine.Network; /// /// Intermediary manager that looks up in it's hierarchy for a to route/broadcast it's received packets to their destinations. /// public class NetworkManager : UniverseObject, INetworkManager { private readonly Dictionary>> clientPacketArrivalMethods = []; private readonly Dictionary>> serverPacketArrivalMethods = []; private readonly Dictionary>> clientPacketRouters = []; private readonly Dictionary>> serverPacketRouters = []; private readonly List<(Type packetType, Delegate callback)> packetRetrievalDelegates = []; private readonly Dictionary _networkEntities = []; public IReadOnlyDictionary NetworkEntities => _networkEntities; private readonly BehaviourCollector _networkEntityCollector = new(); public IBehaviourCollector NetworkEntityCollector => _networkEntityCollector; private INetworkCommunicator _networkCommunicator = null!; public INetworkCommunicator NetworkCommunicator { get => _networkCommunicator; set { if (_networkCommunicator == value) return; var previousCommunicator = _networkCommunicator; _networkCommunicator = value; if (previousCommunicator is not null) UnsubscribeCommunicatorMethods(previousCommunicator); if (_networkCommunicator is not null) SubscribeCommunicatorMethods(_networkCommunicator); } } #region Communicator Subscriptions private void SubscribeCommunicatorMethods(INetworkCommunicator networkCommunicator) { MethodInfo subscribeToPacketsMethod = typeof(INetworkCommunicator) .GetMethod(nameof(INetworkCommunicator.SubscribeToPackets), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; foreach ((Type packetType, Delegate callback) in packetRetrievalDelegates) { MethodInfo genericSubscribeMethod = subscribeToPacketsMethod.MakeGenericMethod(packetType); genericSubscribeMethod.Invoke(networkCommunicator, [callback]); } } private void UnsubscribeCommunicatorMethods(INetworkCommunicator networkCommunicator) { MethodInfo unsubscribeFromPacketsMethod = typeof(INetworkCommunicator) .GetMethod(nameof(INetworkCommunicator.UnsubscribeFromPackets), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; foreach ((Type packetType, Delegate callback) in packetRetrievalDelegates) { MethodInfo genericUnsubscribeMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType); genericUnsubscribeMethod.Invoke(networkCommunicator, [callback]); } } #endregion #region Packet Routing private void OnPacketReceived(string senderClientId, T entityDataPacket) { if (entityDataPacket is IEntityNetworkPacket entityPacket) RoutePacket(senderClientId, entityDataPacket, entityPacket); else BroadcastPacket(senderClientId, entityDataPacket); } private void RoutePacket(string senderClientId, T entityDataPacket, IEntityNetworkPacket entityPacket) { if (NetworkCommunicator is INetworkCommunicatorClient) RoutePacket(clientPacketRouters, entityPacket.EntityId, senderClientId, entityDataPacket); if (NetworkCommunicator is INetworkCommunicatorServer) RoutePacket(serverPacketRouters, entityPacket.EntityId, senderClientId, entityDataPacket); } private void BroadcastPacket(string senderClientId, T entityDataPacket) { if (NetworkCommunicator is INetworkCommunicatorClient) BroadcastPacket(clientPacketRouters, senderClientId, entityDataPacket); if (NetworkCommunicator is INetworkCommunicatorServer) BroadcastPacket(serverPacketRouters, senderClientId, entityDataPacket); } private static void BroadcastPacket( Dictionary>> packetRouters, string senderClientId, T entityDataPacket) { if (!packetRouters.TryGetValue(entityDataPacket!.GetType(), out Dictionary>? routers)) return; foreach ((string id, Event routerEvent) in routers) routerEvent.Invoke(senderClientId, entityDataPacket!); } private static void RoutePacket( Dictionary>> packetRouters, string entityId, string senderClientId, T entityDataPacket) { if (!packetRouters.TryGetValue(entityDataPacket!.GetType(), out Dictionary>? routers)) return; if (!routers.TryGetValue(entityId, out Event? routerEvent)) return; routerEvent.Invoke(senderClientId, entityDataPacket!); } #endregion #region Packet Routers private static void RegisterPacketRoutersFor( INetworkEntity behaviour, Dictionary>> packetRouters, Dictionary>> packetArrivalMethods, NetworkType networkType) { if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary>? arrivalMethods)) return; foreach ((Type packetType, List methods) in arrivalMethods) foreach (MethodInfo receiveMethod in methods) { if (!packetRouters.TryGetValue(packetType, out Dictionary>? routers)) { routers = []; packetRouters.Add(packetType, routers); } Event packetListenerEvent = new(); RegisterPacketListenerEvent(behaviour, packetListenerEvent, receiveMethod, networkType); routers.Add(behaviour.Id, packetListenerEvent); } } private static void RegisterPacketListenerEvent( INetworkEntity behaviour, Event packetListenerEvent, MethodInfo receiveMethod, NetworkType networkType) { switch (networkType) { case NetworkType.Client: packetListenerEvent.AddListener((sender, @object) => receiveMethod.Invoke(behaviour, [@object])); break; case NetworkType.Server: packetListenerEvent.AddListener((sender, @object) => receiveMethod.Invoke(behaviour, [sender, @object])); break; } } private static void UnregisterPacketRoutersFor( INetworkEntity behaviour, Dictionary>> packetRouters, Dictionary>> packetArrivalMethods) { if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary>? arrivalMethods)) return; foreach ((Type packetType, List methods) in arrivalMethods) { if (!packetRouters.TryGetValue(packetType, out Dictionary>? routers)) continue; if (!routers.TryGetValue(behaviour.Id, out Event? routerEvent)) continue; routers.Remove(behaviour.Id); routerEvent.Clear(); } } #endregion #region Engine Callbacks private void OnCollected(IBehaviourCollector sender, IBehaviourCollector.BehaviourCollectedArguments args) { INetworkEntity collectedBehaviour = args.BehaviourCollected; if (!_networkEntities.TryAdd(collectedBehaviour.Id, collectedBehaviour)) throw new($"Unable to add {collectedBehaviour.Id} to {nameof(NetworkManager)}"); RegisterPacketRoutersFor(collectedBehaviour, clientPacketRouters, clientPacketArrivalMethods, NetworkType.Client); RegisterPacketRoutersFor(collectedBehaviour, serverPacketRouters, serverPacketArrivalMethods, NetworkType.Server); } private void OnRemoved(IBehaviourCollector sender, IBehaviourCollector.BehaviourRemovedArguments args) { INetworkEntity removedBehaviour = args.BehaviourRemoved; if (!_networkEntities.Remove(args.BehaviourRemoved.Id)) return; UnregisterPacketRoutersFor(removedBehaviour, clientPacketRouters, clientPacketArrivalMethods); UnregisterPacketRoutersFor(removedBehaviour, serverPacketRouters, serverPacketArrivalMethods); } protected override void OnExitingUniverse(IUniverse universe) => _networkEntityCollector.Unassign(); protected override void OnEnteringUniverse(IUniverse universe) { _networkEntityCollector.Assign(universe); NetworkCommunicator = this.GetRequiredUniverseObjectInParent(); } #endregion #region Initialization public NetworkManager() { CachePacketRetrievalDelegates(); CachePacketArrivalMethods(); _networkEntityCollector.OnCollected.AddListener(OnCollected); _networkEntityCollector.OnRemoved.AddListener(OnRemoved); } private void CachePacketRetrievalDelegates() { IEnumerable packetTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()) .Where(t => typeof(INetworkPacket).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType); MethodInfo onPacketArrivedMethod = GetType() .GetMethod(nameof(OnPacketReceived), BindingFlags.NonPublic | BindingFlags.Instance)!; foreach (Type packetType in packetTypes) { MethodInfo genericOnPacketArrivedMethod = onPacketArrivedMethod.MakeGenericMethod(packetType); Type genericDelegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(string), packetType); Delegate genericPacketReceivedDelegate = Delegate.CreateDelegate(genericDelegateType, this, genericOnPacketArrivedMethod); packetRetrievalDelegates.Add((packetType, genericPacketReceivedDelegate)); } } private void CachePacketArrivalMethods() { CachePacketArrivalMethods(clientPacketArrivalMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient.OnClientPacketArrived)); CachePacketArrivalMethods(serverPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer.OnServerPacketArrived)); } private static void CachePacketArrivalMethods(Dictionary>> packetArrivalMethods, Type listenerType, string packetArrivalMethodName) { foreach (Type listenerClass in GetGenericsWith(listenerType)) { Dictionary> packetRouters = []; packetArrivalMethods.Add(listenerClass, packetRouters); foreach (Type packetListener in GetGenericInterfacesWith(listenerType, listenerClass)) { Type packetType = packetListener.GetGenericArguments().First(); List arrivalMethods = packetListener .GetMethods() .Where(m => m.Name == packetArrivalMethodName) .ToList(); packetRouters.Add(packetType, arrivalMethods); } } } private static IEnumerable GetGenericsWith(Type type) => AppDomain.CurrentDomain .GetAssemblies() .SelectMany(a => a.GetTypes().Where( t => t.GetInterfaces().Any( i => i.IsGenericType && i.GetGenericTypeDefinition() == type ) ) ); private static IEnumerable GetGenericInterfacesWith(Type interfaceType, Type type) => type.GetInterfaces().Where( i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType ); #endregion private enum NetworkType { Client, Server } }