using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Engine.Core; 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>> clientBroadcastPacketArrivalMethods = []; private readonly Dictionary>> serverBroadcastPacketArrivalMethods = []; private readonly Dictionary> clientBroadcastPacketRouters = []; private readonly Dictionary> serverBroadcastPacketRouters = []; 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 registerBroadcastPacketListenersMethods = []; private readonly Dictionary registerEntityPacketListenersMethods = []; private readonly Dictionary _networkEntities = []; public IReadOnlyDictionary NetworkEntities => _networkEntities; private readonly BehaviourCollector _networkEntityCollector = new(); public IBehaviourCollector NetworkEntityCollector => _networkEntityCollector; public INetworkCommunicator NetworkCommunicator { get; set { if (field == value) return; INetworkCommunicator? previousCommunicator = field; field = value; if (previousCommunicator is not null) InvokeCommunicatorMethods(previousCommunicator, nameof(INetworkCommunicator.UnsubscribeFromPackets)); if (field is not null) InvokeCommunicatorMethods(field, nameof(INetworkCommunicator.SubscribeToPackets)); } } = null!; /// Used to delegate subscription and unsubscription methods on the to . private void InvokeCommunicatorMethods(INetworkCommunicator networkCommunicator, string name) { MethodInfo unsubscribeFromPacketsMethod = typeof(INetworkCommunicator) .GetMethod(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; /// 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 genericMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType); genericMethod.Invoke(networkCommunicator, [@delegate]); } } #region Packet Routing/Broadcasting private void OnPacketReceived(IConnection sender, T entityDataPacket) { BroadcastPacket(sender, entityDataPacket); if (entityDataPacket is IEntityNetworkPacket entityPacket) RoutePacket(sender, entityDataPacket, entityPacket); } private void RoutePacket(IConnection sender, T entityDataPacket, IEntityNetworkPacket entityPacket) { if (NetworkCommunicator is INetworkCommunicatorClient) RoutePacket(clientEntityPacketRouters, entityPacket.EntityId, sender, entityDataPacket); if (NetworkCommunicator is INetworkCommunicatorServer) RoutePacket(serverEntityPacketRouters, entityPacket.EntityId, sender, entityDataPacket); } private void BroadcastPacket(IConnection sender, T entityDataPacket) { if (NetworkCommunicator is INetworkCommunicatorClient) BroadcastPacket(clientBroadcastPacketRouters, sender, entityDataPacket); if (NetworkCommunicator is INetworkCommunicatorServer) BroadcastPacket(serverBroadcastPacketRouters, sender, entityDataPacket); } private void BroadcastPacket( Dictionary> packetRouters, IConnection sender, T entityDataPacket) { if (!packetRouters.TryGetValue(entityDataPacket!.GetType(), out Dictionary? routers)) return; foreach ((string behaviourId, object routerEventReference) in routers) { Event routerEvent = (Event)routerEventReference; routerEvent.Invoke(sender, entityDataPacket!); } } private void RoutePacket( Dictionary> packetRouters, string entityId, IConnection sender, T entityDataPacket) { if (!packetRouters.TryGetValue(entityDataPacket!.GetType(), out Dictionary? routers)) return; if (!routers.TryGetValue(entityId, out object? routerEventReference)) return; Event routerEvent = (Event)routerEventReference; routerEvent.Invoke(sender, entityDataPacket!); } #endregion #region Packet Routers private void RegisterPacketRoutersFor( INetworkEntity behaviour, Dictionary> packetRouters, Dictionary>> packetArrivalMethods, NetworkType networkType, Dictionary registerPacketListenersMethods) { if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary>? arrivalMethods)) return; foreach (Type packetType in arrivalMethods.Keys) { if (!packetRouters.TryGetValue(packetType, out Dictionary? routers)) { 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, Dictionary registerPacketListenersMethods) { Type genericEventType = typeof(Event<,>).MakeGenericType(typeof(IConnection), packetType); object packetListenerEvent = Activator.CreateInstance(genericEventType)!; if (!registerPacketListenersMethods.TryGetValue(packetType, out MethodInfo? registerPacketListenerMethod)) throw new($"Packet Listener Events for {packetType.Name} has not been cached."); registerPacketListenerMethod.Invoke(this, [behaviour, packetListenerEvent, networkType]); return packetListenerEvent; } private static void RegisterPacketListenerEvent( INetworkEntity behaviour, Event packetListenerEvent, NetworkType networkType) { switch (networkType) { case NetworkType.Client: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerClient)behaviour).OnClientPacketArrived(sender, packet)); break; case NetworkType.Server: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerServer)behaviour).OnServerPacketArrived(sender, packet)); break; } } 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, Dictionary>> packetArrivalMethods) { if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary>? arrivalMethods)) return; foreach ((Type packetType, List methods) in arrivalMethods) { if (!packetRouters.TryGetValue(packetType, out Dictionary? 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(object routerEventReference) { Event routerEvent = (Event)routerEventReference; routerEvent.Clear(); } #endregion #region Engine Callbacks private void OnCollected(IBehaviourCollector sender, IBehaviourCollector.BehaviourCollectedArguments args) { INetworkEntity collectedBehaviour = args.BehaviourCollected; if (!_networkEntities.TryAdd(collectedBehaviour.Id, collectedBehaviour)) throw new($"Unable to add {collectedBehaviour.Id} to {nameof(NetworkManager)}"); 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) { INetworkEntity removedBehaviour = args.BehaviourRemoved; if (!_networkEntities.Remove(args.BehaviourRemoved.Id)) return; UnregisterPacketRoutersFor(removedBehaviour, clientBroadcastPacketRouters, clientBroadcastPacketArrivalMethods); UnregisterPacketRoutersFor(removedBehaviour, clientEntityPacketRouters, clientEntityPacketArrivalMethods); UnregisterPacketRoutersFor(removedBehaviour, serverBroadcastPacketRouters, serverBroadcastPacketArrivalMethods); UnregisterPacketRoutersFor(removedBehaviour, serverEntityPacketRouters, serverEntityPacketArrivalMethods); } public void ExitUniverse(IUniverse universe) => _networkEntityCollector.Unassign(); public void EnterUniverse(IUniverse universe) { _networkEntityCollector.Assign(universe); NetworkCommunicator = BehaviourController.GetRequiredBehaviourInParent(); } #endregion #region Initialization public NetworkManager() { CachePacketRetrievalDelegates(typeof(INetworkPacket), broadcastPacketRetrievalDelegates); CachePacketRetrievalDelegates(typeof(IEntityNetworkPacket), entityPacketRetrievalDelegates); CacheRegistrationMethods(); CachePacketArrivalMethods(); _networkEntityCollector.OnCollected.AddListener(OnCollected); _networkEntityCollector.OnRemoved.AddListener(OnRemoved); } private void CachePacketRetrievalDelegates(Type packetType, List<(Type packetType, Delegate @delegate)> retrievalDelegates) { IEnumerable packetTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()) .Where(t => packetType.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType); MethodInfo onPacketArrivedMethod = GetType() .GetMethod(nameof(OnPacketReceived), BindingFlags.NonPublic | BindingFlags.Instance)!; foreach (Type pType in packetTypes) { MethodInfo genericOnPacketArrivedMethod = onPacketArrivedMethod.MakeGenericMethod(pType); Type genericDelegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(IConnection), pType); Delegate genericPacketReceivedDelegate = Delegate.CreateDelegate(genericDelegateType, this, genericOnPacketArrivedMethod); retrievalDelegates.Add((pType, genericPacketReceivedDelegate)); } } private void CacheRegistrationMethods() { CacheRegistrationMethods(registerBroadcastPacketListenersMethods, nameof(RegisterPacketListenerEvent), broadcastPacketRetrievalDelegates); CacheRegistrationMethods(registerEntityPacketListenersMethods, nameof(RegisterEntityPacketListenerEvent), entityPacketRetrievalDelegates); CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter), broadcastPacketRetrievalDelegates); } 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.TryAdd(packetType, genericMethod); } } private void CachePacketArrivalMethods() { 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) { foreach (Type listenerClass in GetGenericsWith(listenerType)) { Dictionary> packetRouters = []; packetArrivalMethods.Add(listenerClass, packetRouters); foreach (Type packetListener in GetGenericInterfacesWith(listenerType, listenerClass)) { Type packetType = packetListener.GetGenericArguments().First(); List arrivalMethods = packetListener .GetMethods() .Where(m => m.Name == packetArrivalMethodName) .ToList(); packetRouters.Add(packetType, arrivalMethods); } } } private static IEnumerable GetGenericsWith(Type type) => AppDomain.CurrentDomain .GetAssemblies() .SelectMany(a => a.GetTypes().Where( t => t.GetInterfaces().Any( i => i.IsGenericType && i.GetGenericTypeDefinition() == type ) ) ); private static IEnumerable GetGenericInterfacesWith(Type interfaceType, Type type) => type.GetInterfaces().Where( i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType ); #endregion private enum NetworkType { Client, Server } }