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 need to peer check this class, I don't exactly remember the state I was in when I was originally writing it and left it uncommented and the current comments are added later on. /// 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 { #region Packet Router/Broadcaster to Listener Delegates /// /// Behaviour Type → Packet Type → List of listener methods (broadcast packets, client-side) /// private readonly Dictionary>> clientBroadcastPacketListenerMethods = []; /// /// Behaviour Type → Packet Type → List of listener methods (broadcast packets, server-side) /// private readonly Dictionary>> serverBroadcastPacketListenerMethods = []; /// /// Behaviour Type → Packet Type → List of listener methods (entity packets, client-side) /// private readonly Dictionary>> clientEntityPacketListenerMethods = []; /// /// Behaviour Type → Packet Type → List of listener methods (entity packets, server-side) /// private readonly Dictionary>> serverEntityPacketListenerMethods = []; #endregion #region Packet Router/Broadcaster Events /// /// Packet Type → Behaviour.Id → Broadcaster Event (broadcast, client-side) /// private readonly Dictionary> clientPacketBroadcastEvents = []; /// /// Packet Type → Behaviour.Id → Broadcaster Event (broadcast, server-side) /// private readonly Dictionary> serverPacketBroadcastEvents = []; /// /// Maps an type to a set of routing events, /// keyed by , for CLIENT entity listeners. /// The packet is routed to the correct instance /// by matching . /// private readonly Dictionary> clientEntityPacketRouterEvents = []; /// /// Maps an type to a set of routing events, /// keyed by , for SERVER entity listeners. /// The packet is routed to the correct instance /// by matching . /// private readonly Dictionary> serverEntityPacketRouterEvents = []; #endregion #region Packet Retrieval Delegates /// /// Stores delegates that connect incoming broadcast packets from /// to . /// These are used to subscribe/unsubscribe from events. /// private readonly List broadcastPacketRetrievalSubscriptionDelegates = []; /// /// Stores delegates that connect incoming entity packets from /// to . /// These are used to subscribe/unsubscribe from events. /// private readonly List entityPacketRetrievalSubscriptionDelegates = []; /// /// Stores delegates that connect incoming all packets from to /// . This is a combination of all subscription /// delegates filtered so there are no duplicates packet entries. /// private readonly List uniqueRetrievalSubscriptionDelegates = []; #endregion #region Method Caches /// /// Packet type → method. /// private readonly Dictionary clearRoutesMethods = []; /// /// Packet type → method. /// private readonly Dictionary registerBroadcastPacketListenersMethods = []; /// /// Packet type → method. /// private readonly Dictionary registerEntityPacketListenersMethods = []; #endregion #region Network Entity Collector /// /// All active network , keyed by . /// private readonly Dictionary _networkEntities = []; public IReadOnlyDictionary NetworkEntities => _networkEntities; /// /// Collector responsible for detecting s entering/leaving the universe. /// private readonly BehaviourCollector _networkEntityCollector = new(); public IBehaviourCollector NetworkEntityCollector => _networkEntityCollector; #endregion #region Network Communicator public INetworkCommunicator NetworkCommunicator { get; set { if (field == value) return; INetworkCommunicator? previousCommunicator = field; field = value; // Unsubscribe packet delegates from old communicator if (previousCommunicator is not null) InvokeCommunicatorMethods(nameof(INetworkCommunicator.UnsubscribeFromPackets), previousCommunicator); // Subscribe packet delegates to new communicator if (field is not null) InvokeCommunicatorMethods(nameof(INetworkCommunicator.SubscribeToPackets), field); } } = null!; /// /// Dynamically invokes /// or /// on the provided for all known packet types. /// private void InvokeCommunicatorMethods(string methodName, INetworkCommunicator networkCommunicator) { MethodInfo unsubscribeFromPacketsMethod = typeof(INetworkCommunicator) .GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; foreach ((Type packetType, Delegate @delegate) in uniqueRetrievalSubscriptionDelegates) { MethodInfo genericMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType); genericMethod.Invoke(networkCommunicator, [@delegate]); } } #endregion //////////////////////////////////////////////////////////////// #region Packet Routing/Broadcasting /// /// Entry point for ALL incoming packets from the . /// 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(clientEntityPacketRouterEvents, entityPacket.EntityId, sender, entityDataPacket); if (NetworkCommunicator is INetworkCommunicatorServer) RoutePacket(serverEntityPacketRouterEvents, entityPacket.EntityId, sender, entityDataPacket); } private void BroadcastPacket(IConnection sender, T entityDataPacket) { if (NetworkCommunicator is INetworkCommunicatorClient) BroadcastPacket(clientPacketBroadcastEvents, sender, entityDataPacket); if (NetworkCommunicator is INetworkCommunicatorServer) BroadcastPacket(serverPacketBroadcastEvents, sender, entityDataPacket); } private void BroadcastPacket( Dictionary> packetBroadcasters, IConnection sender, T entityDataPacket) { if (!packetBroadcasters.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 /// /// Registers routing events for the behaviour based on cached packet listener methods. /// private void RegisterPacketRoutersFor( INetworkEntity behaviour, Dictionary> packetRouters, Dictionary>> packetListenerMethods, NetworkType networkType, Dictionary registerPacketListenerListenersMethods) { if (!packetListenerMethods.TryGetValue(behaviour.GetType(), out Dictionary>? listenerMethods)) return; foreach (Type packetType in listenerMethods.Keys) { if (!packetRouters.TryGetValue(packetType, out Dictionary? routers)) { routers = []; packetRouters.Add(packetType, routers); } object packetListenerEvent = CreateEventAndRegister(packetType, behaviour, networkType, registerPacketListenerListenersMethods); routers.Add(behaviour.Id, packetListenerEvent); } } /// /// Creates an Event and attaches listener callbacks. /// 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; } /// /// Registers broadcast packet listeners on the behaviour. /// private static void RegisterBroadcastPacketListenerEvent( 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; } } /// /// Registers entity-specific packet listeners on a network behaviour. /// 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; } } /// /// Unregisters all routing events associated with the behaviour. /// private void UnregisterPacketRoutersFor( INetworkEntity behaviour, Dictionary> packetRouters, Dictionary>> packetListenerMethods) { if (!packetListenerMethods.TryGetValue(behaviour.GetType(), out Dictionary>? listenerMethods)) return; foreach ((Type packetType, List methods) in listenerMethods) { 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]); } } /// /// Clears all listeners from a router event. /// private static void ClearRouter(object routerEventReference) { Event routerEvent = (Event)routerEventReference; routerEvent.Clear(); } #endregion #region Engine Callbacks /// /// Called when an enters the universe. /// Registers all packet routing for that entity. /// 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, clientPacketBroadcastEvents, clientBroadcastPacketListenerMethods, NetworkType.Client, registerBroadcastPacketListenersMethods); RegisterPacketRoutersFor(collectedBehaviour, clientEntityPacketRouterEvents, clientEntityPacketListenerMethods, NetworkType.Client, registerEntityPacketListenersMethods); RegisterPacketRoutersFor(collectedBehaviour, serverPacketBroadcastEvents, serverBroadcastPacketListenerMethods, NetworkType.Server, registerBroadcastPacketListenersMethods); RegisterPacketRoutersFor(collectedBehaviour, serverEntityPacketRouterEvents, serverEntityPacketListenerMethods, NetworkType.Server, registerEntityPacketListenersMethods); } /// /// Called when an is removed from the universe. /// Cleans up all routing. /// private void OnRemoved(IBehaviourCollector sender, IBehaviourCollector.BehaviourRemovedArguments args) { INetworkEntity removedBehaviour = args.BehaviourRemoved; if (!_networkEntities.Remove(removedBehaviour.Id)) return; UnregisterPacketRoutersFor(removedBehaviour, clientPacketBroadcastEvents, clientBroadcastPacketListenerMethods); UnregisterPacketRoutersFor(removedBehaviour, clientEntityPacketRouterEvents, clientEntityPacketListenerMethods); UnregisterPacketRoutersFor(removedBehaviour, serverPacketBroadcastEvents, serverBroadcastPacketListenerMethods); UnregisterPacketRoutersFor(removedBehaviour, serverEntityPacketRouterEvents, serverEntityPacketListenerMethods); } public void ExitUniverse(IUniverse universe) => _networkEntityCollector.Unassign(); public void EnterUniverse(IUniverse universe) { _networkEntityCollector.Assign(universe); NetworkCommunicator = BehaviourController.GetRequiredBehaviourInParent(); } #endregion #region Initialization public NetworkManager() { CacheRetrievalSubscriptionDelegates(); CacheRegistrationMethods(); CachePacketListenerMethods(); _networkEntityCollector.OnCollected.AddListener(OnCollected); _networkEntityCollector.OnRemoved.AddListener(OnRemoved); } /// /// Caches all retrieval subscription delegates for packets. /// private void CacheRetrievalSubscriptionDelegates() { CachePacketRetrievalDelegates(typeof(INetworkPacket), broadcastPacketRetrievalSubscriptionDelegates); CachePacketRetrievalDelegates(typeof(IEntityNetworkPacket), entityPacketRetrievalSubscriptionDelegates); uniqueRetrievalSubscriptionDelegates.AddRange(broadcastPacketRetrievalSubscriptionDelegates.Concat(entityPacketRetrievalSubscriptionDelegates).DistinctBy(pair => pair.PacketType)); } /// /// Creates delegates for all concrete packet types that forward packets to . /// private void CachePacketRetrievalDelegates(Type packetType, List 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 type in packetTypes) { MethodInfo genericOnPacketArrivedMethod = onPacketArrivedMethod.MakeGenericMethod(type); Type genericDelegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(IConnection), type); Delegate genericPacketReceivedDelegate = Delegate.CreateDelegate(genericDelegateType, this, genericOnPacketArrivedMethod); retrievalDelegates.Add((type, genericPacketReceivedDelegate)); } } /// /// Caches all registration and cleanup methods for packets. /// private void CacheRegistrationMethods() { CacheRegistrationMethods(registerBroadcastPacketListenersMethods, nameof(RegisterBroadcastPacketListenerEvent), broadcastPacketRetrievalSubscriptionDelegates); CacheRegistrationMethods(registerEntityPacketListenersMethods, nameof(RegisterEntityPacketListenerEvent), entityPacketRetrievalSubscriptionDelegates); CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter), uniqueRetrievalSubscriptionDelegates); } /// /// Creates generic method instances for each packet type listener to be registered into the . /// private void CacheRegistrationMethods( Dictionary listenerRegistrationMethods, string methodName, List packetRetrievalDelegates) { MethodInfo registerPacketMethod = typeof(NetworkManager).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static)!; foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates) { MethodInfo genericMethod = registerPacketMethod.MakeGenericMethod(packetType); listenerRegistrationMethods.TryAdd(packetType, genericMethod); } } /// /// Caches packet listener methods for all packet listener interfaces. /// private void CachePacketListenerMethods() { CachePacketListenerMethods(clientBroadcastPacketListenerMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<>.OnClientPacketArrived)); CachePacketListenerMethods(serverBroadcastPacketListenerMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer<>.OnServerPacketArrived)); CachePacketListenerMethods(clientEntityPacketListenerMethods, typeof(IPacketListenerClientEntity<>), nameof(IPacketListenerClientEntity<>.OnEntityClientPacketArrived)); CachePacketListenerMethods(serverEntityPacketListenerMethods, typeof(IPacketListenerServerEntity<>), nameof(IPacketListenerServerEntity<>.OnEntityServerPacketArrived)); } /// /// Discovers all types implementing a given packet listener interface and caches their methods. /// private static void CachePacketListenerMethods( Dictionary>> packetListenerMethods, Type listenerType, string packetListenerMethodName) { foreach (Type listenerClass in GetGenericsWith(listenerType)) { Dictionary> listenerMethodDictionary = []; packetListenerMethods.Add(listenerClass, listenerMethodDictionary); foreach (Type packetListener in GetGenericInterfacesWith(listenerType, listenerClass)) { Type packetType = packetListener.GetGenericArguments().First(); List listenerMethods = [.. packetListener.GetMethods().Where(m => m.Name == packetListenerMethodName)]; listenerMethodDictionary.Add(packetType, listenerMethods); } } } /// /// Finds all types that implement a generic interface. /// private static IEnumerable GetGenericsWith(Type type) => AppDomain.CurrentDomain .GetAssemblies() .SelectMany(a => a.GetTypes().Where( t => t.GetInterfaces().Any( i => i.IsGenericType && i.GetGenericTypeDefinition() == type))); // Gets all generic interfaces of a specific definition on a type private static IEnumerable GetGenericInterfacesWith(Type interfaceType, Type type) => type.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType); #endregion // Identifies whether packet routing is client-side or server-side private enum NetworkType { Client, Server } private readonly record struct PacketRetrievalDelegatePair(Type PacketType, Delegate Delegate) { public static implicit operator (Type packetType, Delegate @delegate)(PacketRetrievalDelegatePair value) => (value.PacketType, value.Delegate); public static implicit operator PacketRetrievalDelegatePair((Type packetType, Delegate @delegate) value) => new(value.packetType, value.@delegate); } }