using System.Collections.Immutable; using System.Linq.Expressions; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using RPG.Network.Proto; using RPG.Services.Gameserver.Modules.Attributes; using RPG.Services.Gameserver.Session; namespace RPG.Services.Gameserver.Modules; internal class ModuleManager { private delegate Task ReqHandler(PlayerSession session, IServiceProvider serviceProvider, ReadOnlyMemory body); private static readonly ImmutableDictionary s_reqHandlers; private delegate Task GmCommandHandler(IServiceProvider provider, PlayerSession session, string[] args); private static readonly ImmutableDictionary> s_gmCommandMap; private static readonly IEnumerable s_moduleTypes; private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; public uint PlayerUID { get; private set; } static ModuleManager() { s_moduleTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsAssignableTo(typeof(BaseModule)) && !t.IsAbstract); s_reqHandlers = MapReqHandlers(); s_gmCommandMap = MapGmCommandHandlers(); } public ModuleManager(IServiceProvider serviceProvider, ILogger logger) { _serviceProvider = serviceProvider; _logger = logger; } public TModule Get() where TModule : BaseModule { return _serviceProvider.GetRequiredService(); } public async Task OnLogin(uint gameUID) { PlayerUID = gameUID; foreach (Type moduleType in s_moduleTypes) { if (_serviceProvider.GetRequiredService(moduleType) is BaseModule module) await module.OnLogin(); } } public async Task HandleGmCommandAsync(PlayerSession session, string[] args) { if (args.Length < 2) return; if (s_gmCommandMap.TryGetValue(args[0], out var map)) { if (map.TryGetValue(args[1], out GmCommandHandler? handler)) { await handler(_serviceProvider, session, args[2..]); } } } public async Task HandleAsync(PlayerSession session, CmdType cmdType, ReadOnlyMemory body) { if (s_reqHandlers.TryGetValue(cmdType, out var handler)) { await handler(session, _serviceProvider, body); _logger.LogInformation("Successfully handled command of type {cmdType}", cmdType); } else { _logger.LogWarning("Handler for command of type {cmdType} not defined!", cmdType); } } private static ImmutableDictionary MapReqHandlers() { var builder = ImmutableDictionary.CreateBuilder(); IEnumerable types = Assembly.GetExecutingAssembly().GetTypes() .Where(type => type.IsAssignableTo(typeof(BaseModule)) && !type.IsAbstract); MethodInfo getServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod("GetRequiredService", [typeof(IServiceProvider)])!; foreach (Type type in types) { IEnumerable methods = type.GetMethods() .Where(method => method.GetCustomAttribute() != null); foreach (MethodInfo method in methods) { OnCommandAttribute attribute = method.GetCustomAttribute()!; ParameterExpression sessionParam = Expression.Parameter(typeof(PlayerSession)); ParameterExpression serviceProviderParam = Expression.Parameter(typeof(IServiceProvider)); ParameterExpression bodyParam = Expression.Parameter(typeof(ReadOnlyMemory)); MethodCallExpression getServiceCall = Expression.Call(getServiceMethod.MakeGenericMethod(type), serviceProviderParam); MethodCallExpression handlerCall = Expression.Call(getServiceCall, method, sessionParam, bodyParam); Expression lambda = Expression.Lambda(handlerCall, sessionParam, serviceProviderParam, bodyParam); builder.Add(attribute.CmdType, lambda.Compile()); } } return builder.ToImmutable(); } private static ImmutableDictionary> MapGmCommandHandlers() { var builder = ImmutableDictionary.CreateBuilder>(); IEnumerable types = Assembly.GetExecutingAssembly().GetTypes() .Where(type => type.IsAssignableTo(typeof(BaseModule)) && !type.IsAbstract); MethodInfo getServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod("GetRequiredService", [typeof(IServiceProvider)])!; foreach (Type type in types) { GMAliasAttribute? aliasAttribute = type.GetCustomAttribute(); if (aliasAttribute == null) continue; var commands = ImmutableDictionary.CreateBuilder(); IEnumerable methods = type.GetMethods() .Where(method => method.GetCustomAttribute() != null); foreach (MethodInfo method in methods) { GMCommandAttribute attribute = method.GetCustomAttribute()!; ParameterExpression serviceProviderParam = Expression.Parameter(typeof(IServiceProvider)); ParameterExpression sessionParam = Expression.Parameter(typeof(PlayerSession)); ParameterExpression argsParam = Expression.Parameter(typeof(string[])); MethodCallExpression getServiceCall = Expression.Call(getServiceMethod.MakeGenericMethod(type), serviceProviderParam); MethodCallExpression handlerCall = Expression.Call(getServiceCall, method, sessionParam, argsParam); Expression lambda = Expression.Lambda(handlerCall, serviceProviderParam, sessionParam, argsParam); commands.Add(attribute.Command, lambda.Compile()); } builder.Add(aliasAttribute.Alias, commands.ToImmutable()); } return builder.ToImmutable(); } }