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
This commit is contained in:
2026-01-30 13:43:48 +03:00
parent 64e7321f0f
commit 72f86478f2
3 changed files with 104 additions and 67 deletions

View File

@@ -0,0 +1,6 @@
namespace Engine.Systems.Network;
public interface IPacketListenerClientEntity<T> : INetworkEntity where T : IEntityNetworkPacket
{
void OnEntityClientPacketArrived(IConnection sender, T packet);
}

View File

@@ -0,0 +1,6 @@
namespace Engine.Systems.Network;
public interface IPacketListenerServerEntity<T> : INetworkEntity where T : IEntityNetworkPacket
{
void OnEntityServerPacketArrived(IConnection sender, T packet);
}

View File

@@ -10,18 +10,28 @@ namespace Engine.Systems.Network;
/// <summary> /// <summary>
/// Intermediary manager that looks up in it's hierarchy for a <see cref="INetworkCommunicator"/> to route/broadcast it's received packets to their destinations. /// Intermediary manager that looks up in it's hierarchy for a <see cref="INetworkCommunicator"/> to route/broadcast it's received packets to their destinations.
/// </summary> /// </summary>
/// 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 public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetworkManager
{ {
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> clientPacketArrivalMethods = []; private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> clientBroadcastPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverPacketArrivalMethods = []; private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverBroadcastPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<string, object>> clientPacketRouters = []; private readonly Dictionary<Type, Dictionary<string, object>> clientBroadcastPacketRouters = [];
private readonly Dictionary<Type, Dictionary<string, object>> serverPacketRouters = []; private readonly Dictionary<Type, Dictionary<string, object>> serverBroadcastPacketRouters = [];
private readonly List<(Type packetType, Delegate @delegate)> packetRetrievalDelegates = []; private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> clientEntityPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverEntityPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<string, object>> clientEntityPacketRouters = [];
private readonly Dictionary<Type, Dictionary<string, object>> serverEntityPacketRouters = [];
private readonly List<(Type packetType, Delegate @delegate)> broadcastPacketRetrievalDelegates = [];
private readonly List<(Type packetType, Delegate @delegate)> entityPacketRetrievalDelegates = [];
private readonly Dictionary<Type, MethodInfo> clearRoutesMethods = []; private readonly Dictionary<Type, MethodInfo> clearRoutesMethods = [];
private readonly Dictionary<Type, MethodInfo> registerPacketListenersMethods = []; private readonly Dictionary<Type, MethodInfo> registerBroadcastPacketListenersMethods = [];
private readonly Dictionary<Type, MethodInfo> registerEntityPacketListenersMethods = [];
private readonly Dictionary<string, INetworkEntity> _networkEntities = []; private readonly Dictionary<string, INetworkEntity> _networkEntities = [];
public IReadOnlyDictionary<string, INetworkEntity> NetworkEntities => _networkEntities; public IReadOnlyDictionary<string, INetworkEntity> NetworkEntities => _networkEntities;
@@ -40,63 +50,55 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
INetworkCommunicator? previousCommunicator = field; INetworkCommunicator? previousCommunicator = field;
field = value; field = value;
if (previousCommunicator is not null) UnsubscribeCommunicatorMethods(previousCommunicator); if (previousCommunicator is not null) InvokeCommunicatorMethods(previousCommunicator, nameof(INetworkCommunicator.UnsubscribeFromPackets));
if (field is not null) SubscribeCommunicatorMethods(field); if (field is not null) InvokeCommunicatorMethods(field, nameof(INetworkCommunicator.SubscribeToPackets));
} }
} = null!; } = null!;
#region Communicator Subscriptions /// Used to delegate subscription and unsubscription methods on the <see cref="INetworkCommunicator"/> to <see cref="OnPacketReceived{T}(IConnection, T)"/>.
private void SubscribeCommunicatorMethods(INetworkCommunicator networkCommunicator) private void InvokeCommunicatorMethods(INetworkCommunicator networkCommunicator, string name)
{
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)
{ {
MethodInfo unsubscribeFromPacketsMethod = typeof(INetworkCommunicator) 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 <see cref="OnPacketReceived{T}(IConnection, T)"/>
/// Because a class might have both <see cref="IPacketListenerClient{T}"/> and <see cref="IPacketListenerClientEntity{T}"/>
/// or <see cref="IPacketListenerServer{T}"/> and <see cref="IPacketListenerServerEntity{T}"/> 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); MethodInfo genericMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType);
genericUnsubscribeMethod.Invoke(networkCommunicator, [@delegate]); genericMethod.Invoke(networkCommunicator, [@delegate]);
} }
} }
#endregion
#region Packet Routing #region Packet Routing/Broadcasting
private void OnPacketReceived<T>(IConnection sender, T entityDataPacket) private void OnPacketReceived<T>(IConnection sender, T entityDataPacket)
{ {
BroadcastPacket(sender, entityDataPacket);
if (entityDataPacket is IEntityNetworkPacket entityPacket) if (entityDataPacket is IEntityNetworkPacket entityPacket)
RoutePacket(sender, entityDataPacket, entityPacket); RoutePacket(sender, entityDataPacket, entityPacket);
else
BroadcastPacket(sender, entityDataPacket);
} }
private void RoutePacket<T>(IConnection sender, T entityDataPacket, IEntityNetworkPacket entityPacket) private void RoutePacket<T>(IConnection sender, T entityDataPacket, IEntityNetworkPacket entityPacket)
{ {
if (NetworkCommunicator is INetworkCommunicatorClient) if (NetworkCommunicator is INetworkCommunicatorClient)
RoutePacket(clientPacketRouters, entityPacket.EntityId, sender, entityDataPacket); RoutePacket(clientEntityPacketRouters, entityPacket.EntityId, sender, entityDataPacket);
if (NetworkCommunicator is INetworkCommunicatorServer) if (NetworkCommunicator is INetworkCommunicatorServer)
RoutePacket(serverPacketRouters, entityPacket.EntityId, sender, entityDataPacket); RoutePacket(serverEntityPacketRouters, entityPacket.EntityId, sender, entityDataPacket);
} }
private void BroadcastPacket<T>(IConnection sender, T entityDataPacket) private void BroadcastPacket<T>(IConnection sender, T entityDataPacket)
{ {
if (NetworkCommunicator is INetworkCommunicatorClient) if (NetworkCommunicator is INetworkCommunicatorClient)
BroadcastPacket(clientPacketRouters, sender, entityDataPacket); BroadcastPacket(clientBroadcastPacketRouters, sender, entityDataPacket);
if (NetworkCommunicator is INetworkCommunicatorServer) if (NetworkCommunicator is INetworkCommunicatorServer)
BroadcastPacket(serverPacketRouters, sender, entityDataPacket); BroadcastPacket(serverBroadcastPacketRouters, sender, entityDataPacket);
} }
private static void BroadcastPacket<T>( private void BroadcastPacket<T>(
Dictionary<Type, Dictionary<string, object>> packetRouters, Dictionary<Type, Dictionary<string, object>> packetRouters,
IConnection sender, IConnection sender,
T entityDataPacket) T entityDataPacket)
@@ -111,7 +113,7 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
} }
} }
private static void RoutePacket<T>( private void RoutePacket<T>(
Dictionary<Type, Dictionary<string, object>> packetRouters, Dictionary<Type, Dictionary<string, object>> packetRouters,
string entityId, string entityId,
IConnection sender, IConnection sender,
@@ -133,13 +135,12 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
INetworkEntity behaviour, INetworkEntity behaviour,
Dictionary<Type, Dictionary<string, object>> packetRouters, Dictionary<Type, Dictionary<string, object>> packetRouters,
Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods, Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods,
NetworkType networkType) NetworkType networkType, Dictionary<Type, MethodInfo> registerPacketListenersMethods)
{ {
if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary<Type, List<MethodInfo>>? arrivalMethods)) if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary<Type, List<MethodInfo>>? arrivalMethods))
return; return;
foreach ((Type packetType, List<MethodInfo> methods) in arrivalMethods) foreach (Type packetType in arrivalMethods.Keys)
foreach (MethodInfo receiveMethod in methods)
{ {
if (!packetRouters.TryGetValue(packetType, out Dictionary<string, object>? routers)) if (!packetRouters.TryGetValue(packetType, out Dictionary<string, object>? routers))
{ {
@@ -147,18 +148,20 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
packetRouters.Add(packetType, routers); packetRouters.Add(packetType, routers);
} }
object packetListenerEvent = CreateEventAndRegister(packetType, behaviour, networkType); object packetListenerEvent =
CreateEventAndRegister(packetType, behaviour, networkType, registerPacketListenersMethods);
routers.Add(behaviour.Id, packetListenerEvent); routers.Add(behaviour.Id, packetListenerEvent);
} }
} }
private object CreateEventAndRegister(Type packetType, INetworkEntity behaviour, NetworkType networkType) private object CreateEventAndRegister(Type packetType, INetworkEntity behaviour, NetworkType networkType, Dictionary<Type, MethodInfo> registerPacketListenersMethods)
{ {
Type genericEventType = typeof(Event<,>).MakeGenericType(typeof(IConnection), packetType); Type genericEventType = typeof(Event<,>).MakeGenericType(typeof(IConnection), packetType);
object packetListenerEvent = Activator.CreateInstance(genericEventType)!; object packetListenerEvent = Activator.CreateInstance(genericEventType)!;
if (!registerPacketListenersMethods.TryGetValue(packetType, out MethodInfo? registerPacketListenerMethod)) 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]); registerPacketListenerMethod.Invoke(this, [behaviour, packetListenerEvent, networkType]);
return packetListenerEvent; return packetListenerEvent;
@@ -176,6 +179,19 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
} }
} }
private static void RegisterEntityPacketListenerEvent<T>(
INetworkEntity behaviour,
Event<IConnection, T> packetListenerEvent,
NetworkType networkType
) where T : IEntityNetworkPacket
{
switch (networkType)
{
case NetworkType.Client: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerClientEntity<T>)behaviour).OnEntityClientPacketArrived(sender, packet)); break;
case NetworkType.Server: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerServerEntity<T>)behaviour).OnEntityServerPacketArrived(sender, packet)); break;
}
}
private void UnregisterPacketRoutersFor( private void UnregisterPacketRoutersFor(
INetworkEntity behaviour, INetworkEntity behaviour,
Dictionary<Type, Dictionary<string, object>> packetRouters, Dictionary<Type, Dictionary<string, object>> packetRouters,
@@ -215,8 +231,11 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
if (!_networkEntities.TryAdd(collectedBehaviour.Id, collectedBehaviour)) if (!_networkEntities.TryAdd(collectedBehaviour.Id, collectedBehaviour))
throw new($"Unable to add {collectedBehaviour.Id} to {nameof(NetworkManager)}"); throw new($"Unable to add {collectedBehaviour.Id} to {nameof(NetworkManager)}");
RegisterPacketRoutersFor(collectedBehaviour, clientPacketRouters, clientPacketArrivalMethods, NetworkType.Client); RegisterPacketRoutersFor(collectedBehaviour, clientBroadcastPacketRouters, clientBroadcastPacketArrivalMethods, NetworkType.Client, registerBroadcastPacketListenersMethods);
RegisterPacketRoutersFor(collectedBehaviour, serverPacketRouters, serverPacketArrivalMethods, NetworkType.Server); 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<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourRemovedArguments args) private void OnRemoved(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourRemovedArguments args)
@@ -225,8 +244,11 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
if (!_networkEntities.Remove(args.BehaviourRemoved.Id)) if (!_networkEntities.Remove(args.BehaviourRemoved.Id))
return; return;
UnregisterPacketRoutersFor(removedBehaviour, clientPacketRouters, clientPacketArrivalMethods); UnregisterPacketRoutersFor(removedBehaviour, clientBroadcastPacketRouters, clientBroadcastPacketArrivalMethods);
UnregisterPacketRoutersFor(removedBehaviour, serverPacketRouters, serverPacketArrivalMethods); UnregisterPacketRoutersFor(removedBehaviour, clientEntityPacketRouters, clientEntityPacketArrivalMethods);
UnregisterPacketRoutersFor(removedBehaviour, serverBroadcastPacketRouters, serverBroadcastPacketArrivalMethods);
UnregisterPacketRoutersFor(removedBehaviour, serverEntityPacketRouters, serverEntityPacketArrivalMethods);
} }
public void ExitUniverse(IUniverse universe) => _networkEntityCollector.Unassign(); public void ExitUniverse(IUniverse universe) => _networkEntityCollector.Unassign();
@@ -240,7 +262,8 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
#region Initialization #region Initialization
public NetworkManager() public NetworkManager()
{ {
CachePacketRetrievalDelegates(); CachePacketRetrievalDelegates(typeof(INetworkPacket), broadcastPacketRetrievalDelegates);
CachePacketRetrievalDelegates(typeof(IEntityNetworkPacket), entityPacketRetrievalDelegates);
CacheRegistrationMethods(); CacheRegistrationMethods();
CachePacketArrivalMethods(); CachePacketArrivalMethods();
@@ -248,45 +271,47 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
_networkEntityCollector.OnRemoved.AddListener(OnRemoved); _networkEntityCollector.OnRemoved.AddListener(OnRemoved);
} }
private void CachePacketRetrievalDelegates() private void CachePacketRetrievalDelegates(Type packetType, List<(Type packetType, Delegate @delegate)> retrievalDelegates)
{ {
IEnumerable<Type> packetTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()) IEnumerable<Type> 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() MethodInfo onPacketArrivedMethod = GetType()
.GetMethod(nameof(OnPacketReceived), BindingFlags.NonPublic | BindingFlags.Instance)!; .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); Delegate genericPacketReceivedDelegate = Delegate.CreateDelegate(genericDelegateType, this, genericOnPacketArrivedMethod);
retrievalDelegates.Add((pType, genericPacketReceivedDelegate));
packetRetrievalDelegates.Add((packetType, genericPacketReceivedDelegate));
} }
} }
private void CacheRegistrationMethods() private void CacheRegistrationMethods()
{ {
CacheRegistrationMethods(registerPacketListenersMethods, nameof(RegisterPacketListenerEvent)); CacheRegistrationMethods(registerBroadcastPacketListenersMethods, nameof(RegisterPacketListenerEvent), broadcastPacketRetrievalDelegates);
CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter)); CacheRegistrationMethods(registerEntityPacketListenersMethods, nameof(RegisterEntityPacketListenerEvent), entityPacketRetrievalDelegates);
CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter), broadcastPacketRetrievalDelegates);
} }
private void CacheRegistrationMethods(Dictionary<Type, MethodInfo> registrationMethods, string methodName) private void CacheRegistrationMethods(Dictionary<Type, MethodInfo> registrationMethods, string methodName, List<(Type packetType, Delegate @delegate)> packetRetrievalDelegates)
{ {
MethodInfo registerPacketMethod = typeof(NetworkManager).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static)!; MethodInfo registerPacketMethod = typeof(NetworkManager).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static)!;
foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates) foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates)
{ {
MethodInfo genericMethod = registerPacketMethod.MakeGenericMethod(packetType); MethodInfo genericMethod = registerPacketMethod.MakeGenericMethod(packetType);
registrationMethods.Add(packetType, genericMethod); registrationMethods.TryAdd(packetType, genericMethod);
} }
} }
private void CachePacketArrivalMethods() private void CachePacketArrivalMethods()
{ {
CachePacketArrivalMethods(clientPacketArrivalMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<INetworkEntity>.OnClientPacketArrived)); CachePacketArrivalMethods(clientBroadcastPacketArrivalMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<>.OnClientPacketArrived));
CachePacketArrivalMethods(serverPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer<INetworkEntity>.OnServerPacketArrived)); 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<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods, Type listenerType, string packetArrivalMethodName) private static void CachePacketArrivalMethods(Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods, Type listenerType, string packetArrivalMethodName)