diff --git a/Engine.Systems/Network/Abstract/IPacketListenerClientEntity.cs b/Engine.Systems/Network/Abstract/IPacketListenerClientEntity.cs new file mode 100644 index 0000000..46f0ed7 --- /dev/null +++ b/Engine.Systems/Network/Abstract/IPacketListenerClientEntity.cs @@ -0,0 +1,6 @@ +namespace Engine.Systems.Network; + +public interface IPacketListenerClientEntity : INetworkEntity where T : IEntityNetworkPacket +{ + void OnEntityClientPacketArrived(IConnection sender, T packet); +} diff --git a/Engine.Systems/Network/Abstract/IPacketListenerServerEntity.cs b/Engine.Systems/Network/Abstract/IPacketListenerServerEntity.cs new file mode 100644 index 0000000..90e0051 --- /dev/null +++ b/Engine.Systems/Network/Abstract/IPacketListenerServerEntity.cs @@ -0,0 +1,6 @@ +namespace Engine.Systems.Network; + +public interface IPacketListenerServerEntity : INetworkEntity where T : IEntityNetworkPacket +{ + void OnEntityServerPacketArrived(IConnection sender, T packet); +} diff --git a/Engine.Systems/Network/NetworkManager.cs b/Engine.Systems/Network/NetworkManager.cs index 0082b2a..4474997 100644 --- a/Engine.Systems/Network/NetworkManager.cs +++ b/Engine.Systems/Network/NetworkManager.cs @@ -10,18 +10,28 @@ namespace Engine.Systems.Network; /// /// Intermediary manager that looks up in it's hierarchy for a to route/broadcast it's received packets to their destinations. /// +/// TODO: I urgently need to add proper comments on this manager, I don't exactly remember the state I was in when I was writing it. +/// It's a fairly complex manager that relies heavily on Reflection and lots of generic method delegation which is making it very hard to read back. public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetworkManager { - private readonly Dictionary>> clientPacketArrivalMethods = []; - private readonly Dictionary>> serverPacketArrivalMethods = []; + private readonly Dictionary>> clientBroadcastPacketArrivalMethods = []; + private readonly Dictionary>> serverBroadcastPacketArrivalMethods = []; - private readonly Dictionary> clientPacketRouters = []; - private readonly Dictionary> serverPacketRouters = []; + private readonly Dictionary> clientBroadcastPacketRouters = []; + private readonly Dictionary> serverBroadcastPacketRouters = []; - private readonly List<(Type packetType, Delegate @delegate)> packetRetrievalDelegates = []; + private readonly Dictionary>> clientEntityPacketArrivalMethods = []; + private readonly Dictionary>> serverEntityPacketArrivalMethods = []; + + private readonly Dictionary> clientEntityPacketRouters = []; + private readonly Dictionary> serverEntityPacketRouters = []; + + private readonly List<(Type packetType, Delegate @delegate)> broadcastPacketRetrievalDelegates = []; + private readonly List<(Type packetType, Delegate @delegate)> entityPacketRetrievalDelegates = []; private readonly Dictionary clearRoutesMethods = []; - private readonly Dictionary registerPacketListenersMethods = []; + private readonly Dictionary registerBroadcastPacketListenersMethods = []; + private readonly Dictionary registerEntityPacketListenersMethods = []; private readonly Dictionary _networkEntities = []; public IReadOnlyDictionary NetworkEntities => _networkEntities; @@ -40,63 +50,55 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork INetworkCommunicator? previousCommunicator = field; field = value; - if (previousCommunicator is not null) UnsubscribeCommunicatorMethods(previousCommunicator); - if (field is not null) SubscribeCommunicatorMethods(field); + if (previousCommunicator is not null) InvokeCommunicatorMethods(previousCommunicator, nameof(INetworkCommunicator.UnsubscribeFromPackets)); + if (field is not null) InvokeCommunicatorMethods(field, nameof(INetworkCommunicator.SubscribeToPackets)); } } = null!; - #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 @delegate) in packetRetrievalDelegates) - { - MethodInfo genericSubscribeMethod = subscribeToPacketsMethod.MakeGenericMethod(packetType); - genericSubscribeMethod.Invoke(networkCommunicator, [@delegate]); - } - } - - private void UnsubscribeCommunicatorMethods(INetworkCommunicator networkCommunicator) + /// Used to delegate subscription and unsubscription methods on the to . + private void InvokeCommunicatorMethods(INetworkCommunicator networkCommunicator, string name) { MethodInfo unsubscribeFromPacketsMethod = typeof(INetworkCommunicator) - .GetMethod(nameof(INetworkCommunicator.UnsubscribeFromPackets), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; + .GetMethod(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; - foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates) + /// Get unique entries by the packetType so we don't get duplicate calls to + /// Because a class might have both and + /// or and together. + IEnumerable<(Type packetType, Delegate @delegate)> distinctRetrievalSubscriptionDelegates = + broadcastPacketRetrievalDelegates.Concat(entityPacketRetrievalDelegates).DistinctBy(pair => pair.packetType); + + foreach ((Type packetType, Delegate @delegate) in distinctRetrievalSubscriptionDelegates) { - MethodInfo genericUnsubscribeMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType); - genericUnsubscribeMethod.Invoke(networkCommunicator, [@delegate]); + MethodInfo genericMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType); + genericMethod.Invoke(networkCommunicator, [@delegate]); } } - #endregion - #region Packet Routing + #region Packet Routing/Broadcasting private void OnPacketReceived(IConnection sender, T entityDataPacket) { + BroadcastPacket(sender, entityDataPacket); if (entityDataPacket is IEntityNetworkPacket entityPacket) RoutePacket(sender, entityDataPacket, entityPacket); - else - BroadcastPacket(sender, entityDataPacket); } private void RoutePacket(IConnection sender, T entityDataPacket, IEntityNetworkPacket entityPacket) { if (NetworkCommunicator is INetworkCommunicatorClient) - RoutePacket(clientPacketRouters, entityPacket.EntityId, sender, entityDataPacket); + RoutePacket(clientEntityPacketRouters, entityPacket.EntityId, sender, entityDataPacket); if (NetworkCommunicator is INetworkCommunicatorServer) - RoutePacket(serverPacketRouters, entityPacket.EntityId, sender, entityDataPacket); + RoutePacket(serverEntityPacketRouters, entityPacket.EntityId, sender, entityDataPacket); } private void BroadcastPacket(IConnection sender, T entityDataPacket) { if (NetworkCommunicator is INetworkCommunicatorClient) - BroadcastPacket(clientPacketRouters, sender, entityDataPacket); + BroadcastPacket(clientBroadcastPacketRouters, sender, entityDataPacket); if (NetworkCommunicator is INetworkCommunicatorServer) - BroadcastPacket(serverPacketRouters, sender, entityDataPacket); + BroadcastPacket(serverBroadcastPacketRouters, sender, entityDataPacket); } - private static void BroadcastPacket( + private void BroadcastPacket( Dictionary> packetRouters, IConnection sender, T entityDataPacket) @@ -111,7 +113,7 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork } } - private static void RoutePacket( + private void RoutePacket( Dictionary> packetRouters, string entityId, IConnection sender, @@ -133,32 +135,33 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork INetworkEntity behaviour, Dictionary> packetRouters, Dictionary>> packetArrivalMethods, - NetworkType networkType) + NetworkType networkType, Dictionary registerPacketListenersMethods) { if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary>? arrivalMethods)) return; - foreach ((Type packetType, List methods) in arrivalMethods) - foreach (MethodInfo receiveMethod in methods) + foreach (Type packetType in arrivalMethods.Keys) + { + if (!packetRouters.TryGetValue(packetType, out Dictionary? routers)) { - if (!packetRouters.TryGetValue(packetType, out Dictionary? routers)) - { - routers = []; - packetRouters.Add(packetType, routers); - } - - object packetListenerEvent = CreateEventAndRegister(packetType, behaviour, networkType); - routers.Add(behaviour.Id, packetListenerEvent); + routers = []; + packetRouters.Add(packetType, routers); } + + object packetListenerEvent = + CreateEventAndRegister(packetType, behaviour, networkType, registerPacketListenersMethods); + + routers.Add(behaviour.Id, packetListenerEvent); + } } - private object CreateEventAndRegister(Type packetType, INetworkEntity behaviour, NetworkType networkType) + private object CreateEventAndRegister(Type packetType, INetworkEntity behaviour, NetworkType networkType, Dictionary registerPacketListenersMethods) { Type genericEventType = typeof(Event<,>).MakeGenericType(typeof(IConnection), packetType); object packetListenerEvent = Activator.CreateInstance(genericEventType)!; if (!registerPacketListenersMethods.TryGetValue(packetType, out MethodInfo? registerPacketListenerMethod)) - throw new($"{nameof(RegisterPacketListenerEvent)} for {packetType.Name} has not been cached."); + throw new($"Packet Listener Events for {packetType.Name} has not been cached."); registerPacketListenerMethod.Invoke(this, [behaviour, packetListenerEvent, networkType]); return packetListenerEvent; @@ -176,6 +179,19 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork } } + private static void RegisterEntityPacketListenerEvent( + INetworkEntity behaviour, + Event packetListenerEvent, + NetworkType networkType + ) where T : IEntityNetworkPacket + { + switch (networkType) + { + case NetworkType.Client: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerClientEntity)behaviour).OnEntityClientPacketArrived(sender, packet)); break; + case NetworkType.Server: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerServerEntity)behaviour).OnEntityServerPacketArrived(sender, packet)); break; + } + } + private void UnregisterPacketRoutersFor( INetworkEntity behaviour, Dictionary> packetRouters, @@ -215,8 +231,11 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork 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); + RegisterPacketRoutersFor(collectedBehaviour, clientBroadcastPacketRouters, clientBroadcastPacketArrivalMethods, NetworkType.Client, registerBroadcastPacketListenersMethods); + RegisterPacketRoutersFor(collectedBehaviour, clientEntityPacketRouters, clientEntityPacketArrivalMethods, NetworkType.Client, registerEntityPacketListenersMethods); + + RegisterPacketRoutersFor(collectedBehaviour, serverBroadcastPacketRouters, serverBroadcastPacketArrivalMethods, NetworkType.Server, registerBroadcastPacketListenersMethods); + RegisterPacketRoutersFor(collectedBehaviour, serverEntityPacketRouters, serverEntityPacketArrivalMethods, NetworkType.Server, registerEntityPacketListenersMethods); } private void OnRemoved(IBehaviourCollector sender, IBehaviourCollector.BehaviourRemovedArguments args) @@ -225,8 +244,11 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork if (!_networkEntities.Remove(args.BehaviourRemoved.Id)) return; - UnregisterPacketRoutersFor(removedBehaviour, clientPacketRouters, clientPacketArrivalMethods); - UnregisterPacketRoutersFor(removedBehaviour, serverPacketRouters, serverPacketArrivalMethods); + UnregisterPacketRoutersFor(removedBehaviour, clientBroadcastPacketRouters, clientBroadcastPacketArrivalMethods); + UnregisterPacketRoutersFor(removedBehaviour, clientEntityPacketRouters, clientEntityPacketArrivalMethods); + + UnregisterPacketRoutersFor(removedBehaviour, serverBroadcastPacketRouters, serverBroadcastPacketArrivalMethods); + UnregisterPacketRoutersFor(removedBehaviour, serverEntityPacketRouters, serverEntityPacketArrivalMethods); } public void ExitUniverse(IUniverse universe) => _networkEntityCollector.Unassign(); @@ -240,7 +262,8 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork #region Initialization public NetworkManager() { - CachePacketRetrievalDelegates(); + CachePacketRetrievalDelegates(typeof(INetworkPacket), broadcastPacketRetrievalDelegates); + CachePacketRetrievalDelegates(typeof(IEntityNetworkPacket), entityPacketRetrievalDelegates); CacheRegistrationMethods(); CachePacketArrivalMethods(); @@ -248,45 +271,47 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork _networkEntityCollector.OnRemoved.AddListener(OnRemoved); } - private void CachePacketRetrievalDelegates() + private void CachePacketRetrievalDelegates(Type packetType, List<(Type packetType, Delegate @delegate)> retrievalDelegates) { IEnumerable packetTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()) - .Where(t => typeof(INetworkPacket).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType); + .Where(t => packetType.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType); MethodInfo onPacketArrivedMethod = GetType() .GetMethod(nameof(OnPacketReceived), BindingFlags.NonPublic | BindingFlags.Instance)!; - foreach (Type packetType in packetTypes) + foreach (Type pType in packetTypes) { - MethodInfo genericOnPacketArrivedMethod = onPacketArrivedMethod.MakeGenericMethod(packetType); + MethodInfo genericOnPacketArrivedMethod = onPacketArrivedMethod.MakeGenericMethod(pType); - Type genericDelegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(IConnection), packetType); + Type genericDelegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(IConnection), pType); Delegate genericPacketReceivedDelegate = Delegate.CreateDelegate(genericDelegateType, this, genericOnPacketArrivedMethod); - - packetRetrievalDelegates.Add((packetType, genericPacketReceivedDelegate)); + retrievalDelegates.Add((pType, genericPacketReceivedDelegate)); } } private void CacheRegistrationMethods() { - CacheRegistrationMethods(registerPacketListenersMethods, nameof(RegisterPacketListenerEvent)); - CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter)); + CacheRegistrationMethods(registerBroadcastPacketListenersMethods, nameof(RegisterPacketListenerEvent), broadcastPacketRetrievalDelegates); + CacheRegistrationMethods(registerEntityPacketListenersMethods, nameof(RegisterEntityPacketListenerEvent), entityPacketRetrievalDelegates); + CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter), broadcastPacketRetrievalDelegates); } - private void CacheRegistrationMethods(Dictionary registrationMethods, string methodName) + private void CacheRegistrationMethods(Dictionary registrationMethods, string methodName, List<(Type packetType, Delegate @delegate)> packetRetrievalDelegates) { MethodInfo registerPacketMethod = typeof(NetworkManager).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static)!; foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates) { MethodInfo genericMethod = registerPacketMethod.MakeGenericMethod(packetType); - registrationMethods.Add(packetType, genericMethod); + registrationMethods.TryAdd(packetType, genericMethod); } } private void CachePacketArrivalMethods() { - CachePacketArrivalMethods(clientPacketArrivalMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient.OnClientPacketArrived)); - CachePacketArrivalMethods(serverPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer.OnServerPacketArrived)); + CachePacketArrivalMethods(clientBroadcastPacketArrivalMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<>.OnClientPacketArrived)); + CachePacketArrivalMethods(serverBroadcastPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer<>.OnServerPacketArrived)); + CachePacketArrivalMethods(clientEntityPacketArrivalMethods, typeof(IPacketListenerClientEntity<>), nameof(IPacketListenerClientEntity<>.OnEntityClientPacketArrived)); + CachePacketArrivalMethods(serverEntityPacketArrivalMethods, typeof(IPacketListenerServerEntity<>), nameof(IPacketListenerServerEntity<>.OnEntityServerPacketArrived)); } private static void CachePacketArrivalMethods(Dictionary>> packetArrivalMethods, Type listenerType, string packetArrivalMethodName)