diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 68dbe0314..dc02d2a43 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -13,28 +13,38 @@ namespace Discord.Commands public static readonly TypeInfo ModuleTypeInfo = typeof(IModuleBase).GetTypeInfo(); ///Value1 - ready types, Value2 - types dependant on Value1. - public static async Task, IReadOnlyList>> SearchAsync(Assembly assembly, CommandService service) + public static async Task<(IReadOnlyList, IReadOnlyDictionary>)> SearchAsync(Assembly assembly, CommandService service) { //store bad parent modules to avoid duplicate error logs. var badModules = new List(); var standardModules = new List(); - var queuedModules = new List(); + var queuedModules = new Dictionary>(); foreach (var typeInfo in assembly.DefinedTypes) { if (badModules.Contains(typeInfo)) continue; - if(typeInfo.IsOuterSubTypeCandidate(out Type parentType)) + if (typeInfo.IsOuterSubTypeCandidate(out var parentType)) { bool subTypeIsValid = await typeInfo.IsValidOuterSubTypeAsync( parentType, autoLoadable: true, logger: service._cmdLogger); + var parentTypeInfo = parentType.GetTypeInfo(); + if (subTypeIsValid) { - queuedModules.Add(typeInfo); + //add outer subtypes to a dependency container. + if(queuedModules.TryGetValue(parentTypeInfo, out var dependencyContainer)) + { + dependencyContainer.Add(typeInfo); + } + else + { + queuedModules.Add(parentTypeInfo, new List { typeInfo }); + } } else { @@ -44,31 +54,31 @@ namespace Discord.Commands } bool typeInfoIsValid = await typeInfo.IsValidModuleType(autoLoadable: true, logger: service._cmdLogger); - if(typeInfoIsValid) + if (typeInfoIsValid) { standardModules.Add(typeInfo); } } - return Tuple.Create, IReadOnlyList>(standardModules, queuedModules); + return (standardModules, queuedModules); } - public static Task> BuildAsync(CommandService service, IServiceProvider services, IDictionary moduleDefs, params TypeInfo[] validTypes) => - BuildAsync(validTypes, service, services, moduleDefs); + public static Task> BuildAsync(CommandService service, IServiceProvider services, params TypeInfo[] validTypes) => + BuildAsync(validTypes, service, services); - public static async Task> BuildAsync(IEnumerable validTypes, CommandService service, IServiceProvider services, IDictionary moduleDefs = null) + public static async Task> BuildAsync( + IEnumerable topLevelTypes, + CommandService service, + IServiceProvider services, + IReadOnlyDictionary> dependencies = null) { - /*if (!validTypes.Any()) - throw new InvalidOperationException("Could not find any valid modules from the given selection");*/ - - var topLevelGroups = validTypes - .Where(x => x.DeclaringType == null || !x.DeclaringType.GetTypeInfo().IsValidModuleDefinition()); - - var builtTypes = new List(); + if (!topLevelTypes.Any()) + throw new InvalidOperationException("Could not find any valid modules from the given selection"); var result = new Dictionary(); + var builtTypes = new List(); - foreach (var typeInfo in topLevelGroups) + foreach (var typeInfo in topLevelTypes) { // TODO: This shouldn't be the case; may be safe to remove? if (result.ContainsKey(typeInfo.AsType())) @@ -76,41 +86,62 @@ namespace Discord.Commands var module = new ModuleBuilder(service, null); - BuildModule(module, typeInfo, service, services, moduleDefs); - BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service, services, moduleDefs); + BuildModule(module, typeInfo, service, services); + BuildSubTypes(module, ResolveDependencies(typeInfo, dependencies), builtTypes, service, services, dependencies); builtTypes.Add(typeInfo); //dont build yet, return as a module dictonary? result[typeInfo.AsType()] = module.Build(service, services); } - string entriesName = moduleDefs == null ? "modules" : "submodules"; - await service._cmdLogger.DebugAsync($"Successfully built {builtTypes.Count} {entriesName}.").ConfigureAwait(false); + await service._cmdLogger.DebugAsync($"Successfully built {result.Count} modules.").ConfigureAwait(false); return result; } - private static void BuildSubTypes(ModuleBuilder builder, IEnumerable subTypes, List builtTypes, CommandService service, IServiceProvider services, IDictionary moduleDefs) + private static void BuildSubTypes(ModuleBuilder builder, IEnumerable subTypes, List builtTypes, CommandService service, IServiceProvider services, IReadOnlyDictionary> dependencies = null) { foreach (var typeInfo in subTypes) { if (!typeInfo.IsValidModuleDefinition()) continue; - + if (builtTypes.Contains(typeInfo)) continue; - - builder.AddModule((module) => + + builder.AddModule((module) => { - BuildModule(module, typeInfo, service, services, moduleDefs); - BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service, services, moduleDefs); + BuildModule(module, typeInfo, service, services); + BuildSubTypes(module, ResolveDependencies(typeInfo, dependencies), builtTypes, service, services); }); builtTypes.Add(typeInfo); } } - private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service, IServiceProvider services, IDictionary moduleDefs) + ///Just a helper method to avoid duplicate code + private static IEnumerable ResolveDependencies(TypeInfo typeInfo, IReadOnlyDictionary> dependencies) + { + IEnumerable childTypes = null; + + if (dependencies != null) + { + if (dependencies.TryGetValue(typeInfo, out var linkedChildTypes)) + { + childTypes = linkedChildTypes; + } + } + + //??= pls when 8.0 + if (childTypes == null) + { + childTypes = typeInfo.DeclaredNestedTypes; + } + + return childTypes; + } + + private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service, IServiceProvider services) { var attributes = typeInfo.GetCustomAttributes(); builder.TypeInfo = typeInfo; @@ -134,20 +165,6 @@ namespace Discord.Commands case GroupAttribute group: builder.Name = builder.Name ?? group.Prefix; builder.Group = group.Prefix; - if(group.ParentModule != null) - { - if(moduleDefs.TryGetValue(group.ParentModule, out var mInfo)) - { - foreach (string parentAlias in mInfo.Aliases) - { - builder.AddAliases(parentAlias); - } - } - else - { - throw new ArgumentNullException($"Parent module was not defined for outer sub type: {typeInfo.FullName}."); - } - } builder.AddAliases(group.Prefix); break; case PreconditionAttribute precondition: @@ -169,7 +186,7 @@ namespace Discord.Commands foreach (var method in validCommands) { - builder.AddCommand((command) => + builder.AddCommand((command) => { BuildCommand(command, typeInfo, method, service, services); }); @@ -179,7 +196,7 @@ namespace Discord.Commands private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service, IServiceProvider serviceprovider) { var attributes = method.GetCustomAttributes(); - + foreach (var attribute in attributes) { switch (attribute) @@ -221,7 +238,7 @@ namespace Discord.Commands int pos = 0, count = parameters.Length; foreach (var paramInfo in parameters) { - builder.AddParameter((parameter) => + builder.AddParameter((parameter) => { BuildParameter(parameter, paramInfo, pos++, count, service, serviceprovider); }); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index c93dec009..02e855d0f 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -203,7 +203,7 @@ namespace Discord.Commands throw new InvalidOperationException($"Type {typeInfo.FullName} cannot be transformed into a module. Look at the logs for more details."); } - var module = (await ModuleClassBuilder.BuildAsync(this, services, _typedModuleDefs, typeInfo).ConfigureAwait(false)).FirstOrDefault(); + var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)).FirstOrDefault(); if (module.Value == default(ModuleInfo)) throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?"); @@ -233,8 +233,8 @@ namespace Discord.Commands await _moduleLock.WaitAsync().ConfigureAwait(false); try { - var types = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false); - var standardModuleDefs = await ModuleClassBuilder.BuildAsync(types.Item1, this, services).ConfigureAwait(false); + (var types, var dependencies) = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false); + var standardModuleDefs = await ModuleClassBuilder.BuildAsync(types, this, services, dependencies).ConfigureAwait(false); foreach (var info in standardModuleDefs) { @@ -242,17 +242,7 @@ namespace Discord.Commands LoadModuleInternal(info.Value); } - var outsideParentGroupModuleDefs = await ModuleClassBuilder.BuildAsync(types.Item2, this, services, standardModuleDefs).ConfigureAwait(false); - - foreach (var info in outsideParentGroupModuleDefs) - { - _typedModuleDefs[info.Key] = info.Value; - LoadModuleInternal(info.Value); - } - var moduleDefs = new List(standardModuleDefs.Values); - moduleDefs.AddRange(outsideParentGroupModuleDefs.Values); - return moduleDefs.ToImmutableArray(); } finally