using System.Collections.Immutable; using System.Linq.Expressions; using System.Reflection; using GameServer.Controllers.Attributes; using Microsoft.Extensions.DependencyInjection; namespace GameServer.Controllers.ChatCommands; internal class ChatCommandManager { private delegate Task ChatCommandDelegate(IServiceProvider serviceProvider, string[] args); private static readonly ImmutableDictionary> s_commandCategories; private static readonly ImmutableArray s_commandDescriptions; private readonly IServiceProvider _serviceProvider; static ChatCommandManager() { (s_commandCategories, s_commandDescriptions) = RegisterCommands(); } public static IEnumerable CommandDescriptions => s_commandDescriptions; public ChatCommandManager(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public async Task InvokeCommandAsync(string category, string command, string[] args) { if (s_commandCategories.TryGetValue(category, out var commands)) { if (commands.TryGetValue(command, out ChatCommandDelegate? commandDelegate)) await commandDelegate(_serviceProvider, args); } } private static (ImmutableDictionary>, ImmutableArray) RegisterCommands() { IEnumerable types = Assembly.GetExecutingAssembly().GetTypes() .Where(type => type.GetCustomAttribute() != null); MethodInfo getServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetRequiredService), [typeof(IServiceProvider)])!; var categories = ImmutableDictionary.CreateBuilder>(); var descriptions = ImmutableArray.CreateBuilder(); foreach (Type type in types) { var commands = ImmutableDictionary.CreateBuilder(); foreach (MethodInfo method in type.GetMethods()) { ChatCommandAttribute? cmdAttribute = method.GetCustomAttribute(); if (cmdAttribute == null) continue; ParameterExpression serviceProviderParam = Expression.Parameter(typeof(IServiceProvider)); ParameterExpression argsParam = Expression.Parameter(typeof(string[])); MethodCallExpression getServiceCall = Expression.Call(getServiceMethod.MakeGenericMethod(type), serviceProviderParam); Expression handlerCall = Expression.Call(getServiceCall, method, argsParam); if (method.ReturnType == typeof(void)) // Allow non-async methods as well handlerCall = Expression.Block(handlerCall, Expression.Constant(Task.CompletedTask)); Expression lambda = Expression.Lambda(handlerCall, serviceProviderParam, argsParam); commands.Add(cmdAttribute.Command, lambda.Compile()); ChatCommandDescAttribute? desc = method.GetCustomAttribute(); if (desc != null) descriptions.Add(desc.Description); } ChatCommandCategoryAttribute categoryAttribute = type.GetCustomAttribute()!; categories.Add(categoryAttribute.Category, commands.ToImmutable()); } return (categories.ToImmutable(), descriptions.ToImmutable()); } }