using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Syntriax.Engine.Core; namespace Syntriax.Engine.Network; /// /// Intermediary manager that looks up in it's hierarchy for a to route/broadcast it's received packets to their destinations. /// public class NetworkManager : Behaviour, INetworkManager { private readonly Dictionary>> clientPacketArrivalMethods = []; private readonly Dictionary>> serverPacketArrivalMethods = []; private readonly Dictionary> clientPacketRouters = []; private readonly Dictionary> serverPacketRouters = []; private readonly List<(Type packetType, Delegate @delegate)> packetRetrievalDelegates = []; private readonly Dictionary clearRoutesMethods = []; private readonly Dictionary registerPacketListenersMethods = []; private readonly Dictionary _networkEntities = []; public IReadOnlyDictionary NetworkEntities => _networkEntities; private readonly BehaviourCollector _networkEntityCollector = new(); public IBehaviourCollector NetworkEntityCollector => _networkEntityCollector; private INetworkCommunicator _networkCommunicator = null!; public INetworkCommunicator NetworkCommunicator { get => _networkCommunicator; set { if (_networkCommunicator == value) return; var 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(string senderClientId, T entityDataPacket) { if (entityDataPacket is IEntityNetworkPacket entityPacket) RoutePacket(senderClientId, entityDataPacket, entityPacket); else BroadcastPacket(senderClientId, entityDataPacket); } private void RoutePacket(string senderClientId, T entityDataPacket, IEntityNetworkPacket entityPacket) { if (NetworkCommunicator is INetworkCommunicatorClient) RoutePacket(clientPacketRouters, entityPacket.EntityId, senderClientId, entityDataPacket); if (NetworkCommunicator is INetworkCommunicatorServer) RoutePacket(serverPacketRouters, entityPacket.EntityId, senderClientId, entityDataPacket); } private void BroadcastPacket(string senderClientId, T entityDataPacket) { if (NetworkCommunicator is INetworkCommunicatorClient) BroadcastPacket(clientPacketRouters, senderClientId, entityDataPacket); if (NetworkCommunicator is INetworkCommunicatorServer) BroadcastPacket(serverPacketRouters, senderClientId, entityDataPacket); } private static void BroadcastPacket( Dictionary> packetRouters, string senderClientId, T entityDataPacket) { if (!packetRouters.TryGetValue(entityDataPacket!.GetType(), out Dictionary? routers)) return; foreach ((string id, object routerEventReference) in routers) { Event routerEvent = (Event)routerEventReference; routerEvent.Invoke(senderClientId, entityDataPacket!); } } private static void RoutePacket( Dictionary> packetRouters, string entityId, string senderClientId, 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(senderClientId, entityDataPacket!); } #endregion #region Packet Routers private void RegisterPacketRoutersFor( INetworkEntity behaviour, Dictionary> packetRouters, Dictionary>> packetArrivalMethods, NetworkType networkType) { if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary>? arrivalMethods)) return; foreach ((Type packetType, List methods) in arrivalMethods) foreach (MethodInfo receiveMethod in methods) { if (!packetRouters.TryGetValue(packetType, out Dictionary? 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(string), 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( INetworkEntity behaviour, Event packetListenerEvent, NetworkType networkType) { switch (networkType) { case NetworkType.Client: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerClient)behaviour).OnClientPacketArrived(packet)); break; case NetworkType.Server: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerServer)behaviour).OnServerPacketArrived(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, clientPacketRouters, clientPacketArrivalMethods, NetworkType.Client); RegisterPacketRoutersFor(collectedBehaviour, serverPacketRouters, serverPacketArrivalMethods, NetworkType.Server); } private void OnRemoved(IBehaviourCollector sender, IBehaviourCollector.BehaviourRemovedArguments args) { INetworkEntity removedBehaviour = args.BehaviourRemoved; if (!_networkEntities.Remove(args.BehaviourRemoved.Id)) return; UnregisterPacketRoutersFor(removedBehaviour, clientPacketRouters, clientPacketArrivalMethods); UnregisterPacketRoutersFor(removedBehaviour, serverPacketRouters, serverPacketArrivalMethods); } protected override void OnExitedUniverse(IUniverse universe) => _networkEntityCollector.Unassign(); protected override void OnEnteredUniverse(IUniverse universe) { _networkEntityCollector.Assign(universe); NetworkCommunicator = BehaviourController.GetRequiredBehaviourInParent(); } #endregion #region Initialization public NetworkManager() { CachePacketRetrievalDelegates(); CacheRegistrationMethods(); CachePacketArrivalMethods(); _networkEntityCollector.OnCollected.AddListener(OnCollected); _networkEntityCollector.OnRemoved.AddListener(OnRemoved); } private void CachePacketRetrievalDelegates() { IEnumerable 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(string), 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 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.OnClientPacketArrived)); CachePacketArrivalMethods(serverPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer.OnServerPacketArrived)); } 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 } }