Original Behaviour was using old methods for detecting entering/exiting universe, they are now all under the same hood and the original is kept for UniverseEntranceManager because it needs to enter the universe without itself. The internal behaviour kept under a subnamespace of "Core.Internal" for the purpose that it might come in handy for other use cases.
333 lines
14 KiB
C#
333 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
using Engine.Core;
|
|
|
|
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>
|
|
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<string, object>> clientPacketRouters = [];
|
|
private readonly Dictionary<Type, Dictionary<string, object>> serverPacketRouters = [];
|
|
|
|
private readonly List<(Type packetType, Delegate @delegate)> packetRetrievalDelegates = [];
|
|
|
|
private readonly Dictionary<Type, MethodInfo> clearRoutesMethods = [];
|
|
private readonly Dictionary<Type, MethodInfo> registerPacketListenersMethods = [];
|
|
|
|
private readonly Dictionary<string, INetworkEntity> _networkEntities = [];
|
|
public IReadOnlyDictionary<string, INetworkEntity> NetworkEntities => _networkEntities;
|
|
|
|
private readonly BehaviourCollector<INetworkEntity> _networkEntityCollector = new();
|
|
public IBehaviourCollector<INetworkEntity> NetworkEntityCollector => _networkEntityCollector;
|
|
|
|
private INetworkCommunicator _networkCommunicator = null!;
|
|
public INetworkCommunicator NetworkCommunicator
|
|
{
|
|
get => _networkCommunicator;
|
|
set
|
|
{
|
|
if (_networkCommunicator == value)
|
|
return;
|
|
|
|
INetworkCommunicator? previousCommunicator = _networkCommunicator;
|
|
_networkCommunicator = value;
|
|
|
|
if (previousCommunicator is not null) UnsubscribeCommunicatorMethods(previousCommunicator);
|
|
if (_networkCommunicator is not null) SubscribeCommunicatorMethods(_networkCommunicator);
|
|
}
|
|
}
|
|
|
|
#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)
|
|
{
|
|
MethodInfo unsubscribeFromPacketsMethod = typeof(INetworkCommunicator)
|
|
.GetMethod(nameof(INetworkCommunicator.UnsubscribeFromPackets), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!;
|
|
|
|
foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates)
|
|
{
|
|
MethodInfo genericUnsubscribeMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType);
|
|
genericUnsubscribeMethod.Invoke(networkCommunicator, [@delegate]);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Packet Routing
|
|
private void OnPacketReceived<T>(IConnection sender, T 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);
|
|
if (NetworkCommunicator is INetworkCommunicatorServer)
|
|
RoutePacket(serverPacketRouters, entityPacket.EntityId, sender, entityDataPacket);
|
|
}
|
|
|
|
private void BroadcastPacket<T>(IConnection sender, T entityDataPacket)
|
|
{
|
|
if (NetworkCommunicator is INetworkCommunicatorClient)
|
|
BroadcastPacket(clientPacketRouters, sender, entityDataPacket);
|
|
if (NetworkCommunicator is INetworkCommunicatorServer)
|
|
BroadcastPacket(serverPacketRouters, sender, entityDataPacket);
|
|
}
|
|
|
|
private static void BroadcastPacket<T>(
|
|
Dictionary<Type, Dictionary<string, object>> packetRouters,
|
|
IConnection sender,
|
|
T entityDataPacket)
|
|
{
|
|
if (!packetRouters.TryGetValue(entityDataPacket!.GetType(), out Dictionary<string, object>? routers))
|
|
return;
|
|
|
|
foreach ((string behaviourId, object routerEventReference) in routers)
|
|
{
|
|
Event<IConnection, T> routerEvent = (Event<IConnection, T>)routerEventReference;
|
|
routerEvent.Invoke(sender, entityDataPacket!);
|
|
}
|
|
}
|
|
|
|
private static void RoutePacket<T>(
|
|
Dictionary<Type, Dictionary<string, object>> packetRouters,
|
|
string entityId,
|
|
IConnection sender,
|
|
T entityDataPacket)
|
|
{
|
|
if (!packetRouters.TryGetValue(entityDataPacket!.GetType(), out Dictionary<string, object>? routers))
|
|
return;
|
|
|
|
if (!routers.TryGetValue(entityId, out object? routerEventReference))
|
|
return;
|
|
|
|
Event<IConnection, T> routerEvent = (Event<IConnection, T>)routerEventReference;
|
|
routerEvent.Invoke(sender, entityDataPacket!);
|
|
}
|
|
#endregion
|
|
|
|
#region Packet Routers
|
|
private void RegisterPacketRoutersFor(
|
|
INetworkEntity behaviour,
|
|
Dictionary<Type, Dictionary<string, object>> packetRouters,
|
|
Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods,
|
|
NetworkType networkType)
|
|
{
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
private object CreateEventAndRegister(Type packetType, INetworkEntity behaviour, NetworkType networkType)
|
|
{
|
|
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.");
|
|
|
|
registerPacketListenerMethod.Invoke(this, [behaviour, packetListenerEvent, networkType]);
|
|
return packetListenerEvent;
|
|
}
|
|
|
|
private static void RegisterPacketListenerEvent<T>(
|
|
INetworkEntity behaviour,
|
|
Event<IConnection, T> packetListenerEvent,
|
|
NetworkType networkType)
|
|
{
|
|
switch (networkType)
|
|
{
|
|
case NetworkType.Client: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerClient<T>)behaviour).OnClientPacketArrived(sender, packet)); break;
|
|
case NetworkType.Server: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerServer<T>)behaviour).OnServerPacketArrived(sender, packet)); break;
|
|
}
|
|
}
|
|
|
|
private void UnregisterPacketRoutersFor(
|
|
INetworkEntity behaviour,
|
|
Dictionary<Type, Dictionary<string, object>> packetRouters,
|
|
Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods)
|
|
{
|
|
if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary<Type, List<MethodInfo>>? arrivalMethods))
|
|
return;
|
|
|
|
foreach ((Type packetType, List<MethodInfo> methods) in arrivalMethods)
|
|
{
|
|
if (!packetRouters.TryGetValue(packetType, out Dictionary<string, object>? routers))
|
|
continue;
|
|
|
|
if (!routers.TryGetValue(behaviour.Id, out object? routerEventReference))
|
|
continue;
|
|
|
|
if (!clearRoutesMethods.TryGetValue(packetType, out MethodInfo? clearRouterMethod))
|
|
continue;
|
|
|
|
clearRouterMethod.Invoke(this, [routerEventReference]);
|
|
}
|
|
}
|
|
|
|
private static void ClearRouter<T>(object routerEventReference)
|
|
{
|
|
Event<IConnection, T> routerEvent = (Event<IConnection, T>)routerEventReference;
|
|
routerEvent.Clear();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Engine Callbacks
|
|
private void OnCollected(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourCollectedArguments args)
|
|
{
|
|
INetworkEntity collectedBehaviour = args.BehaviourCollected;
|
|
|
|
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);
|
|
}
|
|
|
|
private void OnRemoved(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourRemovedArguments args)
|
|
{
|
|
INetworkEntity removedBehaviour = args.BehaviourRemoved;
|
|
if (!_networkEntities.Remove(args.BehaviourRemoved.Id))
|
|
return;
|
|
|
|
UnregisterPacketRoutersFor(removedBehaviour, clientPacketRouters, clientPacketArrivalMethods);
|
|
UnregisterPacketRoutersFor(removedBehaviour, serverPacketRouters, serverPacketArrivalMethods);
|
|
}
|
|
|
|
public void ExitUniverse(IUniverse universe) => _networkEntityCollector.Unassign();
|
|
public void EnterUniverse(IUniverse universe)
|
|
{
|
|
_networkEntityCollector.Assign(universe);
|
|
NetworkCommunicator = BehaviourController.GetRequiredBehaviourInParent<INetworkCommunicator>();
|
|
}
|
|
#endregion
|
|
|
|
#region Initialization
|
|
public NetworkManager()
|
|
{
|
|
CachePacketRetrievalDelegates();
|
|
CacheRegistrationMethods();
|
|
CachePacketArrivalMethods();
|
|
|
|
_networkEntityCollector.OnCollected.AddListener(OnCollected);
|
|
_networkEntityCollector.OnRemoved.AddListener(OnRemoved);
|
|
}
|
|
|
|
private void CachePacketRetrievalDelegates()
|
|
{
|
|
IEnumerable<Type> packetTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
|
|
.Where(t => typeof(INetworkPacket).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType);
|
|
|
|
MethodInfo onPacketArrivedMethod = GetType()
|
|
.GetMethod(nameof(OnPacketReceived), BindingFlags.NonPublic | BindingFlags.Instance)!;
|
|
|
|
foreach (Type packetType in packetTypes)
|
|
{
|
|
MethodInfo genericOnPacketArrivedMethod = onPacketArrivedMethod.MakeGenericMethod(packetType);
|
|
|
|
Type genericDelegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(IConnection), packetType);
|
|
Delegate genericPacketReceivedDelegate = Delegate.CreateDelegate(genericDelegateType, this, genericOnPacketArrivedMethod);
|
|
|
|
packetRetrievalDelegates.Add((packetType, genericPacketReceivedDelegate));
|
|
}
|
|
}
|
|
|
|
private void CacheRegistrationMethods()
|
|
{
|
|
CacheRegistrationMethods(registerPacketListenersMethods, nameof(RegisterPacketListenerEvent));
|
|
CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter));
|
|
}
|
|
|
|
private void CacheRegistrationMethods(Dictionary<Type, MethodInfo> registrationMethods, string methodName)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
private void CachePacketArrivalMethods()
|
|
{
|
|
CachePacketArrivalMethods(clientPacketArrivalMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<INetworkEntity>.OnClientPacketArrived));
|
|
CachePacketArrivalMethods(serverPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer<INetworkEntity>.OnServerPacketArrived));
|
|
}
|
|
|
|
private static void CachePacketArrivalMethods(Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods, Type listenerType, string packetArrivalMethodName)
|
|
{
|
|
foreach (Type listenerClass in GetGenericsWith(listenerType))
|
|
{
|
|
Dictionary<Type, List<MethodInfo>> packetRouters = [];
|
|
packetArrivalMethods.Add(listenerClass, packetRouters);
|
|
|
|
foreach (Type packetListener in GetGenericInterfacesWith(listenerType, listenerClass))
|
|
{
|
|
Type packetType = packetListener.GetGenericArguments().First();
|
|
|
|
List<MethodInfo> arrivalMethods = packetListener
|
|
.GetMethods()
|
|
.Where(m => m.Name == packetArrivalMethodName)
|
|
.ToList();
|
|
|
|
packetRouters.Add(packetType, arrivalMethods);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<Type> GetGenericsWith(Type type)
|
|
=> AppDomain.CurrentDomain
|
|
.GetAssemblies()
|
|
.SelectMany(a =>
|
|
a.GetTypes().Where(
|
|
t => t.GetInterfaces().Any(
|
|
i => i.IsGenericType && i.GetGenericTypeDefinition() == type
|
|
)
|
|
)
|
|
);
|
|
|
|
private static IEnumerable<Type> GetGenericInterfacesWith(Type interfaceType, Type type)
|
|
=> type.GetInterfaces().Where(
|
|
i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType
|
|
);
|
|
#endregion
|
|
|
|
private enum NetworkType { Client, Server }
|
|
}
|