diff --git a/Shared/Network/NetworkManager.cs b/Shared/Network/NetworkManager.cs index b86c6fd..1294e23 100644 --- a/Shared/Network/NetworkManager.cs +++ b/Shared/Network/NetworkManager.cs @@ -7,32 +7,18 @@ 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 INetworkCommunicator networkCommunicator = null!; + private readonly Dictionary>> clientPacketArrivalMethods = []; + private readonly Dictionary>> serverPacketArrivalMethods = []; - private readonly Dictionary>> clientListenerDelegates = []; - private readonly Dictionary>> serverListenerDelegates = []; + private readonly Dictionary>> clientPacketRouters = []; + private readonly Dictionary>> serverPacketRouters = []; - private readonly Dictionary>> clientPacketListeners = []; - private readonly Dictionary>> serverPacketListeners = []; - private readonly List<(Type packetType, Delegate callback)> delegates = []; - - public INetworkCommunicator NetworkCommunicator - { - get => networkCommunicator; - set - { - if (networkCommunicator == value) - return; - - var previousCommunicator = networkCommunicator; - networkCommunicator = value; - - if (previousCommunicator is not null) UnsubscribeDelegates(networkCommunicator); - if (networkCommunicator is not null) SubscribeDelegates(networkCommunicator); - } - } + private readonly List<(Type packetType, Delegate callback)> packetRetrievalDelegates = []; private readonly Dictionary _networkEntities = []; public IReadOnlyDictionary NetworkEntities => _networkEntities; @@ -40,45 +26,205 @@ public class NetworkManager : UniverseObject, INetworkManager 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) SubscribeCommunicatorMethods(previousCommunicator); + if (_networkCommunicator is not null) UnsubscribeCommunicatorMethods(_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() { - CachePacketDelegates(); - CacheListenerDelegates(); + CachePacketRetrievalDelegates(); + CachePacketArrivalMethods(); _networkEntityCollector.OnCollected.AddListener(OnCollected); _networkEntityCollector.OnRemoved.AddListener(OnRemoved); } - private void CacheListenerDelegates() + private void CachePacketRetrievalDelegates() { - foreach (Type clientListenerClass in GetClassesImplementing(typeof(IPacketListenerClient<>))) - { - Dictionary> clientInterfaceListeners = []; - clientListenerDelegates.Add(clientListenerClass, clientInterfaceListeners); - foreach (Type clientListenerInterface in GetInterfacesImplementing(clientListenerClass, typeof(IPacketListenerClient<>))) - { - Type clientListenerParameterType = clientListenerInterface.GetGenericArguments().First(); - List delegateDataList = clientListenerInterface.GetMethods().Where(m => m.Name == nameof(IPacketListenerClient.OnClientPacketArrived)).Select(m => new DelegateData(clientListenerParameterType, m)).ToList(); - clientInterfaceListeners.Add(clientListenerParameterType, delegateDataList); - } - } - - foreach (Type serverListenerClass in GetClassesImplementing(typeof(IPacketListenerServer<>))) - { - Dictionary> serverInterfaceListeners = []; - serverListenerDelegates.Add(serverListenerClass, serverInterfaceListeners); - foreach (Type serverListenerInterface in GetInterfacesImplementing(serverListenerClass, typeof(IPacketListenerServer<>))) - { - Type serverListenerParameterType = serverListenerInterface.GetGenericArguments().First(); - List delegateDataList = serverListenerInterface.GetMethods().Where(m => m.Name == nameof(IPacketListenerServer.OnServerPacketArrived)).Select(m => new DelegateData(serverListenerParameterType, m)).ToList(); - serverInterfaceListeners.Add(serverListenerParameterType, delegateDataList); - } - } - } - - private void CachePacketDelegates() - { - // Find network packets implementing INetworkPacket IEnumerable packetTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()) .Where(t => typeof(INetworkPacket).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType); @@ -92,37 +238,38 @@ public class NetworkManager : UniverseObject, INetworkManager Type genericDelegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(string), packetType); Delegate genericPacketReceivedDelegate = Delegate.CreateDelegate(genericDelegateType, this, genericOnPacketArrivedMethod); - delegates.Add((packetType, genericPacketReceivedDelegate)); + packetRetrievalDelegates.Add((packetType, genericPacketReceivedDelegate)); } } - private void SubscribeDelegates(INetworkCommunicator networkCommunicator) + private void CachePacketArrivalMethods() { - MethodInfo subscribeToPacketsMethod = typeof(INetworkCommunicator) - .GetMethod(nameof(INetworkCommunicator.SubscribeToPackets), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; - - foreach ((Type packetType, Delegate callback) in delegates) - { - MethodInfo genericSubscribeMethod = subscribeToPacketsMethod.MakeGenericMethod(packetType); - - genericSubscribeMethod.Invoke(networkCommunicator, [callback]); - } + CachePacketArrivalMethods(clientPacketArrivalMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient.OnClientPacketArrived)); + CachePacketArrivalMethods(serverPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer.OnServerPacketArrived)); } - private void UnsubscribeDelegates(INetworkCommunicator networkCommunicator) + private static void CachePacketArrivalMethods(Dictionary>> packetArrivalMethods, Type listenerType, string packetArrivalMethodName) { - MethodInfo unsubscribeFromPacketsMethod = typeof(INetworkCommunicator) - .GetMethod(nameof(INetworkCommunicator.UnsubscribeFromPackets), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; - - foreach ((Type packetType, Delegate callback) in delegates) + foreach (Type listenerClass in GetGenericsWith(listenerType)) { - MethodInfo genericUnsubscribeMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType); + Dictionary> packetRouters = []; + packetArrivalMethods.Add(listenerClass, packetRouters); - genericUnsubscribeMethod.Invoke(networkCommunicator, [callback]); + 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 GetClassesImplementing(Type type) + private static IEnumerable GetGenericsWith(Type type) => AppDomain.CurrentDomain .GetAssemblies() .SelectMany(a => @@ -133,124 +280,11 @@ public class NetworkManager : UniverseObject, INetworkManager ) ); - private static IEnumerable GetInterfacesImplementing(Type type, Type interfaceType) + private static IEnumerable GetGenericInterfacesWith(Type interfaceType, Type type) => type.GetInterfaces().Where( i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType ); + #endregion - private void OnPacketReceived(string senderClientId, T entityDataPacket) - { - Type packetType = typeof(T); - - if (entityDataPacket is IEntityNetworkPacket entityPacket) - { - if (networkCommunicator is INetworkCommunicatorClient) - if (clientPacketListeners.TryGetValue(packetType, out Dictionary>? clientListeners)) - if (clientListeners.TryGetValue(entityPacket.EntityId, out Event? clientListenerData)) - clientListenerData.Invoke(senderClientId, entityDataPacket!); - - if (networkCommunicator is INetworkCommunicatorServer) - if (serverPacketListeners.TryGetValue(packetType, out Dictionary>? serverListeners)) - if (serverListeners.TryGetValue(entityPacket.EntityId, out Event? serverListenerData)) - serverListenerData.Invoke(senderClientId, entityDataPacket!); - - return; - } - - if (networkCommunicator is INetworkCommunicatorClient) - if (clientPacketListeners.TryGetValue(packetType, out Dictionary>? clientListeners)) - foreach ((string id, Event clientListenerData) in clientListeners) - clientListenerData.Invoke(senderClientId, entityDataPacket!); - - if (networkCommunicator is INetworkCommunicatorServer) - if (serverPacketListeners.TryGetValue(packetType, out Dictionary>? serverListeners)) - foreach ((string id, Event serverListenerData) in serverListeners) - serverListenerData.Invoke(senderClientId, entityDataPacket!); - } - - 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)}"); - - if (clientListenerDelegates.TryGetValue(collectedBehaviour.GetType(), out Dictionary>? clientInterfaceDelegates)) - foreach ((Type clientListenerInterfaceType, List clientDelegateDataList) in clientInterfaceDelegates) - foreach ((Type parameterType, MethodInfo receiveMethod) in clientDelegateDataList) - { - if (!clientPacketListeners.TryGetValue(parameterType, out Dictionary>? clientListeners)) - { - clientListeners = []; - clientPacketListeners.Add(parameterType, clientListeners); - } - - Event clientListenerEvent = new(); - clientListenerEvent.AddListener((sender, @object) => receiveMethod.Invoke(collectedBehaviour, [@object])); - clientListeners.Add(collectedBehaviour.Id, clientListenerEvent); - } - - if (serverListenerDelegates.TryGetValue(collectedBehaviour.GetType(), out Dictionary>? serverInterfaceDelegates)) - foreach ((Type serverListenerInterfaceType, List serverDelegateDataList) in serverInterfaceDelegates) - foreach ((Type parameterType, MethodInfo receiveMethod) in serverDelegateDataList) - { - if (!serverPacketListeners.TryGetValue(parameterType, out Dictionary>? serverListeners)) - { - serverListeners = []; - serverPacketListeners.Add(parameterType, serverListeners); - } - - Event serverListenerEvent = new(); - serverListenerEvent.AddListener((sender, @object) => receiveMethod.Invoke(collectedBehaviour, [sender, @object])); - serverListeners.Add(collectedBehaviour.Id, serverListenerEvent); - } - } - - private void OnRemoved(IBehaviourCollector sender, IBehaviourCollector.BehaviourRemovedArguments args) - { - INetworkEntity removedBehaviour = args.BehaviourRemoved; - if (!_networkEntities.Remove(args.BehaviourRemoved.Id)) - return; - - if (clientListenerDelegates.TryGetValue(removedBehaviour.GetType(), out Dictionary>? clientInterfaceDelegates)) - foreach ((Type clientListenerInterfaceType, List clientDelegateDataList) in clientInterfaceDelegates) - foreach ((Type parameterType, MethodInfo receiveMethod) in clientDelegateDataList) - { - if (!clientPacketListeners.TryGetValue(parameterType, out Dictionary>? clientListeners)) - continue; - - if (!clientListeners.TryGetValue(removedBehaviour.Id, out Event? clientListenerEvent)) - continue; - - clientListeners.Remove(removedBehaviour.Id); - clientListenerEvent.Clear(); - } - - if (serverListenerDelegates.TryGetValue(removedBehaviour.GetType(), out Dictionary>? serverInterfaceDelegates)) - foreach ((Type serverListenerInterfaceType, List serverDelegateDataList) in serverInterfaceDelegates) - foreach ((Type parameterType, MethodInfo receiveMethod) in serverDelegateDataList) - { - if (!serverPacketListeners.TryGetValue(parameterType, out Dictionary>? serverListeners)) - continue; - - if (!serverListeners.TryGetValue(removedBehaviour.Id, out Event? serverListenerEvent)) - continue; - - serverListeners.Remove(removedBehaviour.Id); - serverListenerEvent.Clear(); - } - } - - protected override void OnExitingUniverse(IUniverse universe) => _networkEntityCollector.Unassign(); - protected override void OnEnteringUniverse(IUniverse universe) - { - _networkEntityCollector.Assign(universe); - NetworkCommunicator = this.GetRequiredUniverseObjectInParent(); - } - - private record struct DelegateData(Type ParameterType, MethodInfo ReceiveMethod) - { - public static implicit operator (Type ParameterType, MethodInfo ReceiveMethod)(DelegateData value) => (value.ParameterType, value.ReceiveMethod); - public static implicit operator DelegateData((Type ParameterType, MethodInfo ReceiveMethod) value) => new(value.ParameterType, value.ReceiveMethod); - } + private enum NetworkType { Client, Server } }