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

@@ -10,18 +10,28 @@ namespace Engine.Systems.Network;
/// <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.
/// </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
{
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> clientPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> clientBroadcastPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverBroadcastPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<string, object>> clientPacketRouters = [];
private readonly Dictionary<Type, Dictionary<string, object>> serverPacketRouters = [];
private readonly Dictionary<Type, Dictionary<string, object>> clientBroadcastPacketRouters = [];
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> registerPacketListenersMethods = [];
private readonly Dictionary<Type, MethodInfo> registerBroadcastPacketListenersMethods = [];
private readonly Dictionary<Type, MethodInfo> registerEntityPacketListenersMethods = [];
private readonly Dictionary<string, INetworkEntity> _networkEntities = [];
public IReadOnlyDictionary<string, INetworkEntity> 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 <see cref="INetworkCommunicator"/> to <see cref="OnPacketReceived{T}(IConnection, T)"/>.
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 <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);
genericUnsubscribeMethod.Invoke(networkCommunicator, [@delegate]);
MethodInfo genericMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType);
genericMethod.Invoke(networkCommunicator, [@delegate]);
}
}
#endregion
#region Packet Routing
#region Packet Routing/Broadcasting
private void OnPacketReceived<T>(IConnection sender, T entityDataPacket)
{
BroadcastPacket(sender, entityDataPacket);
if (entityDataPacket is IEntityNetworkPacket entityPacket)
RoutePacket(sender, entityDataPacket, entityPacket);
else
BroadcastPacket(sender, entityDataPacket);
}
private void RoutePacket<T>(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<T>(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<T>(
private void BroadcastPacket<T>(
Dictionary<Type, Dictionary<string, object>> packetRouters,
IConnection sender,
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,
string entityId,
IConnection sender,
@@ -133,32 +135,33 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
INetworkEntity behaviour,
Dictionary<Type, Dictionary<string, object>> packetRouters,
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))
return;
foreach ((Type packetType, List<MethodInfo> methods) in arrivalMethods)
foreach (MethodInfo receiveMethod in methods)
foreach (Type packetType in arrivalMethods.Keys)
{
if (!packetRouters.TryGetValue(packetType, out Dictionary<string, object>? routers))
{
if (!packetRouters.TryGetValue(packetType, out Dictionary<string, object>? 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<Type, MethodInfo> 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<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(
INetworkEntity behaviour,
Dictionary<Type, Dictionary<string, object>> 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<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.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<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()
.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<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)!;
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<INetworkEntity>.OnClientPacketArrived));
CachePacketArrivalMethods(serverPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer<INetworkEntity>.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<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods, Type listenerType, string packetArrivalMethodName)