Browse Source

Parent modules are now correctly bounded with outer subtypes.

Tuples are changed to ValueTuples due to a recent legacy support drop.

ModuleClassBuilder.SearchAsync now returns an IReadOnlyDictionary<TypeInfo, List<TypeInfo>> where List<TypeInfo> contains types that key is the owner of.
(In other words, indexing with typeinfo returns outer subtypes of that parent).
pull/1399/head
LtLi0n 5 years ago
parent
commit
01ae949b37
2 changed files with 65 additions and 58 deletions
  1. +62
    -45
      src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
  2. +3
    -13
      src/Discord.Net.Commands/CommandService.cs

+ 62
- 45
src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs View File

@@ -13,28 +13,38 @@ namespace Discord.Commands
public static readonly TypeInfo ModuleTypeInfo = typeof(IModuleBase).GetTypeInfo();

///<summary>Value1 - ready types, Value2 - types dependant on Value1.</summary>
public static async Task<Tuple<IReadOnlyList<TypeInfo>, IReadOnlyList<TypeInfo>>> SearchAsync(Assembly assembly, CommandService service)
public static async Task<(IReadOnlyList<TypeInfo>, IReadOnlyDictionary<TypeInfo, List<TypeInfo>>)> SearchAsync(Assembly assembly, CommandService service)
{
//store bad parent modules to avoid duplicate error logs.
var badModules = new List<TypeInfo>();
var standardModules = new List<TypeInfo>();
var queuedModules = new List<TypeInfo>();
var queuedModules = new Dictionary<TypeInfo, List<TypeInfo>>();

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> { 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<TypeInfo>, IReadOnlyList<TypeInfo>>(standardModules, queuedModules);
return (standardModules, queuedModules);
}

public static Task<Dictionary<Type, ModuleInfo>> BuildAsync(CommandService service, IServiceProvider services, IDictionary<Type, ModuleInfo> moduleDefs, params TypeInfo[] validTypes) =>
BuildAsync(validTypes, service, services, moduleDefs);
public static Task<Dictionary<Type, ModuleInfo>> BuildAsync(CommandService service, IServiceProvider services, params TypeInfo[] validTypes) =>
BuildAsync(validTypes, service, services);

public static async Task<Dictionary<Type, ModuleInfo>> BuildAsync(IEnumerable<TypeInfo> validTypes, CommandService service, IServiceProvider services, IDictionary<Type, ModuleInfo> moduleDefs = null)
public static async Task<Dictionary<Type, ModuleInfo>> BuildAsync(
IEnumerable<TypeInfo> topLevelTypes,
CommandService service,
IServiceProvider services,
IReadOnlyDictionary<TypeInfo, List<TypeInfo>> 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<TypeInfo>();
if (!topLevelTypes.Any())
throw new InvalidOperationException("Could not find any valid modules from the given selection");

var result = new Dictionary<Type, ModuleInfo>();
var builtTypes = new List<TypeInfo>();

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<TypeInfo> subTypes, List<TypeInfo> builtTypes, CommandService service, IServiceProvider services, IDictionary<Type, ModuleInfo> moduleDefs)
private static void BuildSubTypes(ModuleBuilder builder, IEnumerable<TypeInfo> subTypes, List<TypeInfo> builtTypes, CommandService service, IServiceProvider services, IReadOnlyDictionary<TypeInfo, List<TypeInfo>> 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<Type, ModuleInfo> moduleDefs)
///<summary>Just a helper method to avoid duplicate code</summary>
private static IEnumerable<TypeInfo> ResolveDependencies(TypeInfo typeInfo, IReadOnlyDictionary<TypeInfo, List<TypeInfo>> dependencies)
{
IEnumerable<TypeInfo> 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);
});


+ 3
- 13
src/Discord.Net.Commands/CommandService.cs View File

@@ -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<ModuleInfo>(standardModuleDefs.Values);
moduleDefs.AddRange(outsideParentGroupModuleDefs.Values);

return moduleDefs.ToImmutableArray();
}
finally


Loading…
Cancel
Save