using System.Collections.Immutable; using System.Linq.Expressions; using System.Reflection; using NahidaImpact.Gameserver.Controllers.Attributes; using NahidaImpact.Gameserver.Controllers.Result; using NahidaImpact.Gameserver.Network; using NahidaImpact.Protocol; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace NahidaImpact.Gameserver.Controllers.Dispatching; internal class NetCommandDispatcher(IServiceProvider serviceProvider, ILogger logger) { private static readonly ImmutableDictionary>> _handlers; private readonly IServiceProvider _serviceProvider = serviceProvider; private readonly ILogger _logger = logger; static NetCommandDispatcher() { _handlers = InitializeHandlers(); } public async ValueTask InvokeHandler(NetPacket packet) { ArgumentNullException.ThrowIfNull(packet, nameof(packet)); if (_handlers.TryGetValue(packet.CmdType, out Func>? handler)) { return await handler(_serviceProvider, packet); } _logger.LogWarning("No handler defined for command of type {cmdType}", packet.CmdType); return null; } private static ImmutableDictionary>> InitializeHandlers() { ImmutableDictionary>>.Builder builder = ImmutableDictionary.CreateBuilder>>(); IEnumerable types = Assembly.GetExecutingAssembly().GetTypes() .Where(type => type.GetCustomAttribute() != null); foreach (Type type in types) { IEnumerable methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public) .Where(method => method.GetCustomAttribute() != null); foreach (MethodInfo method in methods) { NetCommandAttribute attribute = method.GetCustomAttribute()!; if (builder.TryGetKey(attribute.CmdType, out _)) throw new Exception($"Handler for command {attribute.CmdType} defined twice!"); builder[attribute.CmdType] = CreateHandlerDelegate(type, method); } } return builder.ToImmutable(); } private static Func> CreateHandlerDelegate(Type controllerType, MethodInfo method) { ParameterExpression serviceProviderParameter = Expression.Parameter(typeof(IServiceProvider), "serviceProvider"); ParameterExpression netPacketParameter = Expression.Parameter(typeof(NetPacket), "packet"); ConstantExpression controllerTypeConstant = Expression.Constant(controllerType); ParameterExpression controllerVariable = Expression.Variable(controllerType, "controller"); PropertyInfo packetProperty = typeof(ControllerBase).GetProperty("Packet")!; MethodInfo createInstanceMethod = typeof(ActivatorUtilities).GetMethod("CreateInstance", [typeof(IServiceProvider), typeof(Type), typeof(object[])])!; List expressionBlock = [ Expression.Assign(controllerVariable, Expression.Convert( Expression.Call(null, createInstanceMethod, serviceProviderParameter, controllerTypeConstant, Expression.Constant(Array.Empty())), controllerType)), Expression.Assign(Expression.Property(controllerVariable, packetProperty), netPacketParameter) ]; List parameterExpressions = []; foreach (ParameterInfo parameter in method.GetParameters()) { MethodInfo getServiceMethod = typeof(ServiceProviderServiceExtensions) .GetMethod("GetRequiredService", [typeof(IServiceProvider)])! .MakeGenericMethod(parameter.ParameterType); parameterExpressions.Add(Expression.Call(getServiceMethod, serviceProviderParameter)); } expressionBlock.Add(Expression.Call(controllerVariable, method, parameterExpressions)); return Expression.Lambda>>( Expression.Block(new[] { controllerVariable }, expressionBlock), serviceProviderParameter, netPacketParameter) .Compile(); } }