From 72f86478f2791e410863727fbd7c67c28f0d1fe5 Mon Sep 17 00:00:00 2001 From: Syntriax Date: Fri, 30 Jan 2026 13:43:48 +0300 Subject: [PATCH] feat!: added broadcast & routing support for NetworkManager It used to only route or broadcast, now the same packet can be both routed to it's own behaviour AND at the same time the broadcast listeners can get alerted of the same package such as universe-wide managers --- .../Abstract/IPacketListenerClientEntity.cs | 6 + .../Abstract/IPacketListenerServerEntity.cs | 6 + Engine.Systems/Network/NetworkManager.cs | 159 ++++++++++-------- 3 files changed, 104 insertions(+), 67 deletions(-) create mode 100644 Engine.Systems/Network/Abstract/IPacketListenerClientEntity.cs create mode 100644 Engine.Systems/Network/Abstract/IPacketListenerServerEntity.cs 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)