perf: reduced network manager memory allocations

This commit is contained in:
Syntriax 2025-05-31 22:53:53 +03:00
parent 8901a5469f
commit f3ff1b74d2

View File

@ -10,6 +10,12 @@ namespace Syntriax.Engine.Network;
public class NetworkManager : UniverseObject, INetworkManager
{
private INetworkCommunicator networkCommunicator = null!;
private readonly Dictionary<Type, Dictionary<Type, List<DelegateData>>> clientListenerDelegates = [];
private readonly Dictionary<Type, Dictionary<Type, List<DelegateData>>> serverListenerDelegates = [];
private readonly Dictionary<Type, Dictionary<string, Event<string, object>>> clientPacketListeners = [];
private readonly Dictionary<Type, Dictionary<string, Event<string, object>>> serverPacketListeners = [];
private readonly List<(Type packetType, Delegate callback)> delegates = [];
public INetworkCommunicator NetworkCommunicator
@ -28,9 +34,6 @@ public class NetworkManager : UniverseObject, INetworkManager
}
}
private readonly Dictionary<Type, Dictionary<string, Event<string, object>>> clientPacketListeners = [];
private readonly Dictionary<Type, Dictionary<string, Event<string, object>>> serverPacketListeners = [];
private readonly Dictionary<string, INetworkEntity> _networkEntities = [];
public IReadOnlyDictionary<string, INetworkEntity> NetworkEntities => _networkEntities;
@ -39,13 +42,41 @@ public class NetworkManager : UniverseObject, INetworkManager
public NetworkManager()
{
CacheDelegates();
CachePacketDelegates();
CacheListenerDelegates();
_networkEntityCollector.OnCollected.AddListener(OnCollected);
_networkEntityCollector.OnRemoved.AddListener(OnRemoved);
}
private void CacheDelegates()
private void CacheListenerDelegates()
{
foreach (Type clientListenerClass in GetClassesImplementing(typeof(IPacketListenerClient<>)))
{
Dictionary<Type, List<DelegateData>> clientInterfaceListeners = [];
clientListenerDelegates.Add(clientListenerClass, clientInterfaceListeners);
foreach (Type clientListenerInterface in GetInterfacesImplementing(clientListenerClass, typeof(IPacketListenerClient<>)))
{
Type clientListenerParameterType = clientListenerInterface.GetGenericArguments().First();
List<DelegateData> delegateDataList = clientListenerInterface.GetMethods().Where(m => m.Name == nameof(IPacketListenerClient<INetworkEntity>.OnClientPacketArrived)).Select(m => new DelegateData(clientListenerParameterType, m)).ToList();
clientInterfaceListeners.Add(clientListenerParameterType, delegateDataList);
}
}
foreach (Type serverListenerClass in GetClassesImplementing(typeof(IPacketListenerServer<>)))
{
Dictionary<Type, List<DelegateData>> serverInterfaceListeners = [];
serverListenerDelegates.Add(serverListenerClass, serverInterfaceListeners);
foreach (Type serverListenerInterface in GetInterfacesImplementing(serverListenerClass, typeof(IPacketListenerServer<>)))
{
Type serverListenerParameterType = serverListenerInterface.GetGenericArguments().First();
List<DelegateData> delegateDataList = serverListenerInterface.GetMethods().Where(m => m.Name == nameof(IPacketListenerServer<INetworkEntity>.OnServerPacketArrived)).Select(m => new DelegateData(serverListenerParameterType, m)).ToList();
serverInterfaceListeners.Add(serverListenerParameterType, delegateDataList);
}
}
}
private void CachePacketDelegates()
{
// Find network packets implementing INetworkPacket
IEnumerable<Type> packetTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
@ -91,6 +122,22 @@ public class NetworkManager : UniverseObject, INetworkManager
}
}
private static IEnumerable<Type> GetClassesImplementing(Type type)
=> AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(a =>
a.GetTypes().Where(
t => t.GetInterfaces().Any(
i => i.IsGenericType && i.GetGenericTypeDefinition() == type
)
)
);
private static IEnumerable<Type> GetInterfacesImplementing(Type type, Type interfaceType)
=> type.GetInterfaces().Where(
i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType
);
private void OnPacketReceived<T>(string senderClientId, T entityDataPacket)
{
Type packetType = typeof(T);
@ -128,37 +175,35 @@ public class NetworkManager : UniverseObject, INetworkManager
if (!_networkEntities.TryAdd(collectedBehaviour.Id, collectedBehaviour))
throw new($"Unable to add {collectedBehaviour.Id} to {nameof(NetworkManager)}");
foreach (Type clientListenerType in collectedBehaviour.GetType().GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPacketListenerClient<>)))
{
Type clientListenerParameterType = clientListenerType.GetGenericArguments().First();
MethodInfo clientListenerReceiveMethod = clientListenerType.GetMethods().First(m => m.Name == nameof(IPacketListenerClient<INetworkEntity>.OnClientPacketArrived));
if (clientListenerDelegates.TryGetValue(collectedBehaviour.GetType(), out Dictionary<Type, List<DelegateData>>? clientInterfaceDelegates))
foreach ((Type clientListenerInterfaceType, List<DelegateData> clientDelegateDataList) in clientInterfaceDelegates)
foreach ((Type parameterType, MethodInfo receiveMethod) in clientDelegateDataList)
{
if (!clientPacketListeners.TryGetValue(parameterType, out Dictionary<string, Event<string, object>>? clientListeners))
{
clientListeners = [];
clientPacketListeners.Add(parameterType, clientListeners);
}
if (!clientPacketListeners.TryGetValue(clientListenerParameterType, out Dictionary<string, Event<string, object>>? clientListeners))
{
clientListeners = [];
clientPacketListeners.Add(clientListenerParameterType, clientListeners);
}
Event<string, object> clientListenerEvent = new();
clientListenerEvent.AddListener((sender, @object) => receiveMethod.Invoke(collectedBehaviour, [@object]));
clientListeners.Add(collectedBehaviour.Id, clientListenerEvent);
}
Event<string, object> clientListenerEvent = new();
clientListenerEvent.AddListener((sender, @object) => clientListenerReceiveMethod.Invoke(collectedBehaviour, [@object]));
clientListeners.Add(collectedBehaviour.Id, clientListenerEvent);
}
if (serverListenerDelegates.TryGetValue(collectedBehaviour.GetType(), out Dictionary<Type, List<DelegateData>>? serverInterfaceDelegates))
foreach ((Type serverListenerInterfaceType, List<DelegateData> serverDelegateDataList) in serverInterfaceDelegates)
foreach ((Type parameterType, MethodInfo receiveMethod) in serverDelegateDataList)
{
if (!serverPacketListeners.TryGetValue(parameterType, out Dictionary<string, Event<string, object>>? serverListeners))
{
serverListeners = [];
serverPacketListeners.Add(parameterType, serverListeners);
}
foreach (Type serverListenerType in collectedBehaviour.GetType().GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPacketListenerServer<>)))
{
Type serverListenerParameterType = serverListenerType.GetGenericArguments().First();
MethodInfo serverListenerReceiveMethod = serverListenerType.GetMethods().First(m => m.Name == nameof(IPacketListenerServer<INetworkEntity>.OnServerPacketArrived));
if (!serverPacketListeners.TryGetValue(serverListenerParameterType, out Dictionary<string, Event<string, object>>? serverListeners))
{
serverListeners = [];
serverPacketListeners.Add(serverListenerParameterType, serverListeners);
}
Event<string, object> serverListenerEvent = new();
serverListenerEvent.AddListener((sender, @object) => serverListenerReceiveMethod.Invoke(collectedBehaviour, [sender, @object]));
serverListeners.Add(collectedBehaviour.Id, serverListenerEvent);
}
Event<string, object> serverListenerEvent = new();
serverListenerEvent.AddListener((sender, @object) => receiveMethod.Invoke(collectedBehaviour, [sender, @object]));
serverListeners.Add(collectedBehaviour.Id, serverListenerEvent);
}
}
private void OnRemoved(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourRemovedArguments args)
@ -172,4 +217,10 @@ public class NetworkManager : UniverseObject, INetworkManager
_networkEntityCollector.Assign(universe);
NetworkCommunicator = this.GetRequiredUniverseObjectInParent<INetworkCommunicator>();
}
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);
}
}