326 lines
13 KiB
C#
326 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
using Syntriax.Engine.Core;
|
|
|
|
namespace Syntriax.Engine.Network;
|
|
|
|
public class NetworkManagerSlo : UniverseObject, INetworkManager
|
|
{
|
|
private readonly List<(Type packetType, Delegate callback)> packetDelegates = [];
|
|
|
|
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 Dictionary<string, INetworkEntity> _networkEntities = [];
|
|
private readonly BehaviourCollector<INetworkEntity> _networkEntityCollector = new();
|
|
|
|
public IReadOnlyDictionary<string, INetworkEntity> NetworkEntities => _networkEntities;
|
|
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)
|
|
UnsubscribeDelegates(previousCommunicator);
|
|
|
|
if (_networkCommunicator is not null)
|
|
SubscribeDelegates(_networkCommunicator);
|
|
}
|
|
}
|
|
|
|
#region Network Communicator Subscriptions
|
|
private static MethodInfo? GetCommunicatorMethod(string methodName)
|
|
=> typeof(INetworkCommunicator).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
|
|
|
private void SubscribeDelegates(INetworkCommunicator communicator)
|
|
{
|
|
MethodInfo subscribeMethod = GetCommunicatorMethod(nameof(INetworkCommunicator.SubscribeToPackets))!;
|
|
|
|
foreach ((Type packetType, Delegate callback) in packetDelegates)
|
|
{
|
|
MethodInfo genericMethod = subscribeMethod.MakeGenericMethod(packetType);
|
|
genericMethod.Invoke(communicator, [callback]);
|
|
}
|
|
}
|
|
|
|
private void UnsubscribeDelegates(INetworkCommunicator communicator)
|
|
{
|
|
MethodInfo unsubscribeMethod = GetCommunicatorMethod(nameof(INetworkCommunicator.UnsubscribeFromPackets))!;
|
|
|
|
foreach ((Type packetType, Delegate callback) in packetDelegates)
|
|
{
|
|
MethodInfo genericMethod = unsubscribeMethod.MakeGenericMethod(packetType);
|
|
genericMethod.Invoke(communicator, [callback]);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Engine Callbacks
|
|
private void OnEntityCollected(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourCollectedArguments args)
|
|
{
|
|
INetworkEntity entity = args.BehaviourCollected;
|
|
|
|
if (!_networkEntities.TryAdd(entity.Id, entity))
|
|
throw new Exception($"Unable to add {entity.Id} to {nameof(NetworkManager)}");
|
|
|
|
RegisterEntityListeners(entity);
|
|
}
|
|
|
|
private void OnEntityRemoved(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourRemovedArguments args)
|
|
{
|
|
INetworkEntity removedBehaviour = args.BehaviourRemoved;
|
|
if (!_networkEntities.Remove(args.BehaviourRemoved.Id))
|
|
return;
|
|
|
|
UnRegisterEntityListeners(removedBehaviour);
|
|
}
|
|
|
|
protected override void OnExitingUniverse(IUniverse universe)
|
|
{
|
|
_networkEntityCollector.Unassign();
|
|
}
|
|
|
|
protected override void OnEnteringUniverse(IUniverse universe)
|
|
{
|
|
_networkEntityCollector.Assign(universe);
|
|
NetworkCommunicator = this.GetRequiredUniverseObjectInParent<INetworkCommunicator>();
|
|
}
|
|
#endregion
|
|
|
|
#region Packet Retrieval
|
|
private void OnPacketReceived<T>(string senderClientId, T packet)
|
|
{
|
|
if (packet is IEntityNetworkPacket entityPacket)
|
|
RouteEntityPacket(senderClientId, entityPacket.EntityId, packet);
|
|
else
|
|
RouteBroadcastPacket(senderClientId, packet!);
|
|
}
|
|
|
|
private void RouteEntityPacket<T>(string senderClientId, string entityId, T packet)
|
|
{
|
|
if (NetworkCommunicator is INetworkCommunicatorClient)
|
|
InvokeListenerIfExists(clientPacketListeners, entityId, senderClientId, packet!);
|
|
|
|
if (NetworkCommunicator is INetworkCommunicatorServer)
|
|
InvokeListenerIfExists(serverPacketListeners, entityId, senderClientId, packet!);
|
|
}
|
|
|
|
private void RouteBroadcastPacket<T>(string senderClientId, T packet)
|
|
{
|
|
if (NetworkCommunicator is INetworkCommunicatorClient)
|
|
InvokeAllListeners(clientPacketListeners, senderClientId, packet!);
|
|
|
|
if (NetworkCommunicator is INetworkCommunicatorServer)
|
|
InvokeAllListeners(serverPacketListeners, senderClientId, packet!);
|
|
}
|
|
|
|
private static void InvokeListenerIfExists<T>(
|
|
Dictionary<Type, Dictionary<string, Event<string, object>>> listeners,
|
|
string entityId,
|
|
string senderClientId,
|
|
T packet)
|
|
{
|
|
if (!listeners.TryGetValue(packet!.GetType(), out Dictionary<string, Event<string, object>>? entityListeners))
|
|
return;
|
|
|
|
if (entityListeners.TryGetValue(entityId, out Event<string, object>? listenerEvent))
|
|
listenerEvent.Invoke(senderClientId, packet);
|
|
}
|
|
|
|
private static void InvokeAllListeners<T>(
|
|
Dictionary<Type, Dictionary<string, Event<string, object>>> listeners,
|
|
string senderClientId,
|
|
T packet)
|
|
{
|
|
if (!listeners.TryGetValue(packet!.GetType(), out Dictionary<string, Event<string, object>>? entityListeners))
|
|
return;
|
|
|
|
foreach ((string _, Event<string, object> listenerEvent) in entityListeners)
|
|
listenerEvent.Invoke(senderClientId, packet);
|
|
}
|
|
#endregion
|
|
|
|
#region Listener Setups
|
|
private void RegisterEntityListeners(INetworkEntity entity)
|
|
{
|
|
RegisterEntityListenersForSide(entity, clientListenerDelegates, clientPacketListeners, isServer: false);
|
|
RegisterEntityListenersForSide(entity, serverListenerDelegates, serverPacketListeners, isServer: true);
|
|
}
|
|
|
|
private static void RegisterEntityListenersForSide(
|
|
INetworkEntity entity,
|
|
Dictionary<Type, Dictionary<Type, List<DelegateData>>> listenerDelegates,
|
|
Dictionary<Type, Dictionary<string, Event<string, object>>> packetListeners,
|
|
bool isServer)
|
|
{
|
|
if (!listenerDelegates.TryGetValue(entity.GetType(), out Dictionary<Type, List<DelegateData>>? interfaceDelegates))
|
|
return;
|
|
|
|
foreach ((Type interfaceType, List<DelegateData> delegateDataList) in interfaceDelegates)
|
|
foreach ((Type parameterType, MethodInfo receiveMethod) in delegateDataList)
|
|
{
|
|
Dictionary<string, Event<string, object>> listeners = GetOrCreateListenersForPacketType(packetListeners, parameterType);
|
|
Event<string, object> listenerEvent = CreateListenerEvent(entity, receiveMethod, isServer);
|
|
listeners.Add(entity.Id, listenerEvent);
|
|
}
|
|
}
|
|
|
|
private void UnRegisterEntityListeners(INetworkEntity entity)
|
|
{
|
|
UnregisterEntityListenersForSide(entity, clientListenerDelegates, clientPacketListeners);
|
|
UnregisterEntityListenersForSide(entity, serverListenerDelegates, serverPacketListeners);
|
|
}
|
|
|
|
private static void UnregisterEntityListenersForSide(
|
|
INetworkEntity entity,
|
|
Dictionary<Type, Dictionary<Type, List<DelegateData>>> listenerDelegates,
|
|
Dictionary<Type, Dictionary<string, Event<string, object>>> packetListeners)
|
|
{
|
|
if (!listenerDelegates.TryGetValue(entity.GetType(), out Dictionary<Type, List<DelegateData>>? interfaceDelegates))
|
|
return;
|
|
|
|
foreach ((Type interfaceType, List<DelegateData> delegateDataList) in interfaceDelegates)
|
|
foreach ((Type parameterType, MethodInfo receiveMethod) in delegateDataList)
|
|
{
|
|
Dictionary<string, Event<string, object>> listeners = GetOrCreateListenersForPacketType(packetListeners, parameterType);
|
|
|
|
if (!listeners.TryGetValue(entity.Id, out Event<string, object>? listenerEvent))
|
|
continue;
|
|
|
|
listeners.Remove(entity.Id);
|
|
listenerDelegates.Clear();
|
|
}
|
|
}
|
|
|
|
private static Dictionary<string, Event<string, object>> GetOrCreateListenersForPacketType(
|
|
Dictionary<Type, Dictionary<string, Event<string, object>>> packetListeners,
|
|
Type packetType)
|
|
{
|
|
if (!packetListeners.TryGetValue(packetType, out Dictionary<string, Event<string, object>>? listeners))
|
|
{
|
|
listeners = [];
|
|
packetListeners.Add(packetType, listeners);
|
|
}
|
|
return listeners;
|
|
}
|
|
|
|
private static Event<string, object> CreateListenerEvent(INetworkEntity entity, MethodInfo receiveMethod, bool isServer)
|
|
{
|
|
Event<string, object> listenerEvent = new();
|
|
|
|
if (isServer)
|
|
listenerEvent.AddListener((sender, packet) => receiveMethod.Invoke(entity, [sender, packet]));
|
|
else
|
|
listenerEvent.AddListener((sender, packet) => receiveMethod.Invoke(entity, [packet]));
|
|
|
|
return listenerEvent;
|
|
}
|
|
#endregion
|
|
|
|
#region Initialization
|
|
private static IEnumerable<Type> DiscoverPacketTypes()
|
|
=> AppDomain.CurrentDomain.GetAssemblies()
|
|
.SelectMany(a => a.GetTypes())
|
|
.Where(
|
|
t => typeof(INetworkPacket).IsAssignableFrom(t)
|
|
&& !t.IsInterface
|
|
&& !t.IsAbstract
|
|
&& !t.IsGenericType
|
|
);
|
|
|
|
private static IEnumerable<Type> DiscoverClassesImplementing(Type interfaceType)
|
|
=> AppDomain.CurrentDomain.GetAssemblies()
|
|
.SelectMany(a => a.GetTypes())
|
|
.Where(
|
|
t => t.GetInterfaces().Any(
|
|
i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType
|
|
)
|
|
);
|
|
|
|
private static IEnumerable<Type> GetInterfacesOfType(Type type, Type interfaceType)
|
|
=> type.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType);
|
|
|
|
private static void CacheListenerDelegatesForSide(Dictionary<Type, Dictionary<Type, List<DelegateData>>> targetCache, Type listenerInterfaceType, string methodName)
|
|
{
|
|
IEnumerable<Type> listenerClasses = DiscoverClassesImplementing(listenerInterfaceType);
|
|
|
|
foreach (Type listenerClass in listenerClasses)
|
|
{
|
|
Dictionary<Type, List<DelegateData>> interfaceListeners = [];
|
|
targetCache.Add(listenerClass, interfaceListeners);
|
|
|
|
IEnumerable<Type> interfaces = GetInterfacesOfType(listenerClass, listenerInterfaceType);
|
|
|
|
foreach (Type listenerInterface in interfaces)
|
|
{
|
|
Type packetType = listenerInterface.GetGenericArguments().First();
|
|
|
|
List<DelegateData> methods = listenerInterface.GetMethods()
|
|
.Where(m => m.Name == methodName)
|
|
.Select(m => new DelegateData(packetType, m))
|
|
.ToList();
|
|
|
|
interfaceListeners.Add(packetType, methods);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CacheListenerDelegates()
|
|
{
|
|
CacheListenerDelegatesForSide(clientListenerDelegates, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<INetworkEntity>.OnClientPacketArrived));
|
|
CacheListenerDelegatesForSide(serverListenerDelegates, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer<INetworkEntity>.OnServerPacketArrived));
|
|
}
|
|
|
|
private void CachePacketDelegates()
|
|
{
|
|
IEnumerable<Type> packetTypes = DiscoverPacketTypes();
|
|
MethodInfo onPacketReceivedMethod = GetType().GetMethod(
|
|
nameof(OnPacketReceived),
|
|
BindingFlags.NonPublic | BindingFlags.Instance
|
|
)!;
|
|
|
|
foreach (Type packetType in packetTypes)
|
|
{
|
|
MethodInfo genericMethod = onPacketReceivedMethod.MakeGenericMethod(packetType);
|
|
Type delegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(string), packetType);
|
|
Delegate packetDelegate = Delegate.CreateDelegate(delegateType, this, genericMethod);
|
|
|
|
packetDelegates.Add((packetType, packetDelegate));
|
|
}
|
|
}
|
|
|
|
public NetworkManagerSlo()
|
|
{
|
|
CachePacketDelegates();
|
|
CacheListenerDelegates();
|
|
|
|
_networkEntityCollector.OnCollected.AddListener(OnEntityCollected);
|
|
_networkEntityCollector.OnRemoved.AddListener(OnEntityRemoved);
|
|
}
|
|
#endregion
|
|
|
|
private readonly 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);
|
|
}
|
|
}
|