Browse Source

Reduced command module lifetime to a single command execution. Removed ModuleAttribute.

tags/1.0-rc
RogueException 8 years ago
parent
commit
635819b89f
16 changed files with 161 additions and 160 deletions
  1. +9
    -0
      src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs
  2. +0
    -8
      src/Discord.Net.Commands/Attributes/InAttribute.cs
  3. +0
    -22
      src/Discord.Net.Commands/Attributes/ModuleAttribute.cs
  4. +1
    -1
      src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs
  5. +1
    -1
      src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs
  6. +1
    -1
      src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs
  7. +23
    -23
      src/Discord.Net.Commands/CommandInfo.cs
  8. +1
    -1
      src/Discord.Net.Commands/CommandParser.cs
  9. +60
    -62
      src/Discord.Net.Commands/CommandService.cs
  10. +4
    -4
      src/Discord.Net.Commands/Map/CommandMap.cs
  11. +5
    -5
      src/Discord.Net.Commands/Map/CommandMapNode.cs
  12. +15
    -0
      src/Discord.Net.Commands/ModuleBase.cs
  13. +22
    -19
      src/Discord.Net.Commands/ModuleInfo.cs
  14. +15
    -9
      src/Discord.Net.Commands/ReflectionUtils.cs
  15. +3
    -3
      src/Discord.Net.Commands/Results/SearchResult.cs
  16. +1
    -1
      src/Discord.Net.Core/Utils/ConcurrentHashSet.cs

+ 9
- 0
src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs View File

@@ -0,0 +1,9 @@
using System;

namespace Discord.Commands
{
[AttributeUsage(AttributeTargets.Class)]
public class DontAutoLoadAttribute : Attribute
{
}
}

+ 0
- 8
src/Discord.Net.Commands/Attributes/InAttribute.cs View File

@@ -1,8 +0,0 @@
using System;

namespace Discord.Commands
{
public class InAttribute : Attribute
{
}
}

+ 0
- 22
src/Discord.Net.Commands/Attributes/ModuleAttribute.cs View File

@@ -1,22 +0,0 @@
using System;

namespace Discord.Commands
{
[AttributeUsage(AttributeTargets.Class)]
public class ModuleAttribute : Attribute
{
public string Prefix { get; }
public bool AutoLoad { get; set; }

public ModuleAttribute()
{
Prefix = null;
AutoLoad = true;
}
public ModuleAttribute(string prefix)
{
Prefix = prefix;
AutoLoad = true;
}
}
}

+ 1
- 1
src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs View File

@@ -6,6 +6,6 @@ namespace Discord.Commands
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public abstract class PreconditionAttribute : Attribute public abstract class PreconditionAttribute : Attribute
{ {
public abstract Task<PreconditionResult> CheckPermissions(CommandContext context, Command executingCommand, object moduleInstance);
public abstract Task<PreconditionResult> CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map);
} }
} }

+ 1
- 1
src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs View File

@@ -21,7 +21,7 @@ namespace Discord.Commands
Contexts = contexts; Contexts = contexts;
} }


public override Task<PreconditionResult> CheckPermissions(CommandContext context, Command executingCommand, object moduleInstance)
public override Task<PreconditionResult> CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map)
{ {
bool isValid = false; bool isValid = false;




+ 1
- 1
src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs View File

@@ -20,7 +20,7 @@ namespace Discord.Commands
GuildPermission = null; GuildPermission = null;
} }
public override Task<PreconditionResult> CheckPermissions(CommandContext context, Command executingCommand, object moduleInstance)
public override Task<PreconditionResult> CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map)
{ {
var guildUser = context.User as IGuildUser; var guildUser = context.User as IGuildUser;




src/Discord.Net.Commands/Command.cs → src/Discord.Net.Commands/CommandInfo.cs View File

@@ -10,16 +10,15 @@ using System.Threading.Tasks;
namespace Discord.Commands namespace Discord.Commands
{ {
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class Command
public class CommandInfo
{ {
private static readonly MethodInfo _convertParamsMethod = typeof(Command).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList));
private static readonly MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList));
private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>(); private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>();

private readonly object _instance;
private readonly Func<CommandContext, IReadOnlyList<object>, Task> _action;
private readonly Func<CommandContext, object[], Task> _action;


public MethodInfo Source { get; } public MethodInfo Source { get; }
public Module Module { get; }
public ModuleInfo Module { get; }
public string Name { get; } public string Name { get; }
public string Summary { get; } public string Summary { get; }
public string Remarks { get; } public string Remarks { get; }
@@ -30,13 +29,12 @@ namespace Discord.Commands
public IReadOnlyList<CommandParameter> Parameters { get; } public IReadOnlyList<CommandParameter> Parameters { get; }
public IReadOnlyList<PreconditionAttribute> Preconditions { get; } public IReadOnlyList<PreconditionAttribute> Preconditions { get; }


internal Command(MethodInfo source, Module module, object instance, CommandAttribute attribute, string groupPrefix)
internal CommandInfo(MethodInfo source, ModuleInfo module, CommandAttribute attribute, string groupPrefix)
{ {
try try
{ {
Source = source; Source = source;
Module = module; Module = module;
_instance = instance;


Name = source.Name; Name = source.Name;


@@ -85,18 +83,18 @@ namespace Discord.Commands
} }
} }


public async Task<PreconditionResult> CheckPreconditions(CommandContext context)
public async Task<PreconditionResult> CheckPreconditions(CommandContext context, IDependencyMap map = null)
{ {
foreach (PreconditionAttribute precondition in Module.Preconditions) foreach (PreconditionAttribute precondition in Module.Preconditions)
{ {
var result = await precondition.CheckPermissions(context, this, Module.Instance).ConfigureAwait(false);
var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false);
if (!result.IsSuccess) if (!result.IsSuccess)
return result; return result;
} }


foreach (PreconditionAttribute precondition in Preconditions) foreach (PreconditionAttribute precondition in Preconditions)
{ {
var result = await precondition.CheckPermissions(context, this, Module.Instance).ConfigureAwait(false);
var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false);
if (!result.IsSuccess) if (!result.IsSuccess)
return result; return result;
} }
@@ -169,11 +167,9 @@ namespace Discord.Commands
private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo) private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo)
{ {
var parameters = methodInfo.GetParameters(); var parameters = methodInfo.GetParameters();
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(CommandContext))
throw new InvalidOperationException($"The first parameter of a command must be {nameof(CommandContext)}.");


var paramBuilder = ImmutableArray.CreateBuilder<CommandParameter>(parameters.Length - 1);
for (int i = 1; i < parameters.Length; i++)
var paramBuilder = ImmutableArray.CreateBuilder<CommandParameter>(parameters.Length);
for (int i = 0; i < parameters.Length; i++)
{ {
var parameter = parameters[i]; var parameter = parameters[i];
var type = parameter.ParameterType; var type = parameter.ParameterType;
@@ -209,19 +205,23 @@ namespace Discord.Commands
} }
return paramBuilder.ToImmutable(); return paramBuilder.ToImmutable();
} }
private Func<CommandContext, IReadOnlyList<object>, Task> BuildAction(MethodInfo methodInfo)
private Func<CommandContext, object[], Task> BuildAction(MethodInfo methodInfo)
{ {
if (methodInfo.ReturnType != typeof(Task)) if (methodInfo.ReturnType != typeof(Task))
throw new InvalidOperationException("Commands must return a non-generic Task."); throw new InvalidOperationException("Commands must return a non-generic Task.");


return (msg, args) =>
return (context, args) =>
{ {
object[] newArgs = new object[args.Count + 1];
newArgs[0] = msg;
for (int i = 0; i < args.Count; i++)
newArgs[i + 1] = args[i];
var result = methodInfo.Invoke(_instance, newArgs);
return result as Task ?? Task.CompletedTask;
var instance = Module.CreateInstance();
instance.Context = context;
try
{
return methodInfo.Invoke(instance, args) as Task ?? Task.CompletedTask;
}
finally
{
(instance as IDisposable)?.Dispose();
}
}; };
} }



+ 1
- 1
src/Discord.Net.Commands/CommandParser.cs View File

@@ -13,7 +13,7 @@ namespace Discord.Commands
QuotedParameter QuotedParameter
} }
public static async Task<ParseResult> ParseArgs(Command command, CommandContext context, string input, int startPos)
public static async Task<ParseResult> ParseArgs(CommandInfo command, CommandContext context, string input, int startPos)
{ {
CommandParameter curParam = null; CommandParameter curParam = null;
StringBuilder argBuilder = new StringBuilder(input.Length); StringBuilder argBuilder = new StringBuilder(input.Length);


+ 60
- 62
src/Discord.Net.Commands/CommandService.cs View File

@@ -11,18 +11,20 @@ namespace Discord.Commands
{ {
public class CommandService public class CommandService
{ {
private static readonly TypeInfo _moduleTypeInfo = typeof(ModuleBase).GetTypeInfo();

private readonly SemaphoreSlim _moduleLock; private readonly SemaphoreSlim _moduleLock;
private readonly ConcurrentDictionary<Type, Module> _modules;
private readonly ConcurrentDictionary<Type, ModuleInfo> _moduleDefs;
private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders; private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders;
private readonly CommandMap _map; private readonly CommandMap _map;


public IEnumerable<Module> Modules => _modules.Select(x => x.Value);
public IEnumerable<Command> Commands => _modules.SelectMany(x => x.Value.Commands);
public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x.Value);
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Value.Commands);


public CommandService() public CommandService()
{ {
_moduleLock = new SemaphoreSlim(1, 1); _moduleLock = new SemaphoreSlim(1, 1);
_modules = new ConcurrentDictionary<Type, Module>();
_moduleDefs = new ConcurrentDictionary<Type, ModuleInfo>();
_map = new CommandMap(); _map = new CommandMap();
_typeReaders = new ConcurrentDictionary<Type, TypeReader> _typeReaders = new ConcurrentDictionary<Type, TypeReader>
{ {
@@ -45,7 +47,6 @@ namespace Discord.Commands
[typeof(IMessage)] = new MessageTypeReader<IMessage>(), [typeof(IMessage)] = new MessageTypeReader<IMessage>(),
[typeof(IUserMessage)] = new MessageTypeReader<IUserMessage>(), [typeof(IUserMessage)] = new MessageTypeReader<IUserMessage>(),
//[typeof(ISystemMessage)] = new MessageTypeReader<ISystemMessage>(),
[typeof(IChannel)] = new ChannelTypeReader<IChannel>(), [typeof(IChannel)] = new ChannelTypeReader<IChannel>(),
[typeof(IDMChannel)] = new ChannelTypeReader<IDMChannel>(), [typeof(IDMChannel)] = new ChannelTypeReader<IDMChannel>(),
[typeof(IGroupChannel)] = new ChannelTypeReader<IGroupChannel>(), [typeof(IGroupChannel)] = new ChannelTypeReader<IGroupChannel>(),
@@ -55,120 +56,99 @@ namespace Discord.Commands
[typeof(ITextChannel)] = new ChannelTypeReader<ITextChannel>(), [typeof(ITextChannel)] = new ChannelTypeReader<ITextChannel>(),
[typeof(IVoiceChannel)] = new ChannelTypeReader<IVoiceChannel>(), [typeof(IVoiceChannel)] = new ChannelTypeReader<IVoiceChannel>(),


//[typeof(IGuild)] = new GuildTypeReader<IGuild>(),

[typeof(IRole)] = new RoleTypeReader<IRole>(), [typeof(IRole)] = new RoleTypeReader<IRole>(),


//[typeof(IInvite)] = new InviteTypeReader<IInvite>(),
//[typeof(IInviteMetadata)] = new InviteTypeReader<IInviteMetadata>(),

[typeof(IUser)] = new UserTypeReader<IUser>(), [typeof(IUser)] = new UserTypeReader<IUser>(),
[typeof(IGroupUser)] = new UserTypeReader<IGroupUser>(), [typeof(IGroupUser)] = new UserTypeReader<IGroupUser>(),
[typeof(IGuildUser)] = new UserTypeReader<IGuildUser>(), [typeof(IGuildUser)] = new UserTypeReader<IGuildUser>(),
}; };
} }


public void AddTypeReader<T>(TypeReader reader)
{
_typeReaders[typeof(T)] = reader;
}
public void AddTypeReader(Type type, TypeReader reader)
{
_typeReaders[type] = reader;
}
internal TypeReader GetTypeReader(Type type)
{
TypeReader reader;
if (_typeReaders.TryGetValue(type, out reader))
return reader;
return null;
}

public async Task<Module> Load(object moduleInstance)
//Modules
public async Task<ModuleInfo> AddModule<T>(IDependencyMap dependencyMap = null)
{ {
await _moduleLock.WaitAsync().ConfigureAwait(false); await _moduleLock.WaitAsync().ConfigureAwait(false);
try try
{ {
if (_modules.ContainsKey(moduleInstance.GetType()))
throw new ArgumentException($"This module has already been loaded.");
if (_moduleDefs.ContainsKey(typeof(T)))
throw new ArgumentException($"This module has already been added.");


var typeInfo = moduleInstance.GetType().GetTypeInfo();
var moduleAttr = typeInfo.GetCustomAttribute<ModuleAttribute>();
if (moduleAttr == null)
throw new ArgumentException($"Modules must be marked with ModuleAttribute.");
var typeInfo = typeof(T).GetTypeInfo();
if (!_moduleTypeInfo.IsAssignableFrom(typeInfo))
throw new ArgumentException($"Modules must inherit ModuleBase.");


return LoadInternal(moduleInstance, moduleAttr, typeInfo, null);
return AddModuleInternal(typeInfo, dependencyMap);
} }
finally finally
{ {
_moduleLock.Release(); _moduleLock.Release();
} }
} }
private Module LoadInternal(object moduleInstance, ModuleAttribute moduleAttr, TypeInfo typeInfo, IDependencyMap dependencyMap)
{
if (_modules.ContainsKey(moduleInstance.GetType()))
return _modules[moduleInstance.GetType()];

var loadedModule = new Module(typeInfo, this, moduleInstance, moduleAttr, dependencyMap);
_modules[moduleInstance.GetType()] = loadedModule;

foreach (var cmd in loadedModule.Commands)
_map.AddCommand(cmd);

return loadedModule;
}
public async Task<IEnumerable<Module>> LoadAssembly(Assembly assembly, IDependencyMap dependencyMap = null)
public async Task<IEnumerable<ModuleInfo>> AddModules(Assembly assembly, IDependencyMap dependencyMap = null)
{ {
var modules = ImmutableArray.CreateBuilder<Module>();
var moduleDefs = ImmutableArray.CreateBuilder<ModuleInfo>();
await _moduleLock.WaitAsync().ConfigureAwait(false); await _moduleLock.WaitAsync().ConfigureAwait(false);
try try
{ {
foreach (var type in assembly.ExportedTypes) foreach (var type in assembly.ExportedTypes)
{ {
var typeInfo = type.GetTypeInfo();
var moduleAttr = typeInfo.GetCustomAttribute<ModuleAttribute>();
if (moduleAttr != null && moduleAttr.AutoLoad)
if (!_moduleDefs.ContainsKey(type))
{ {
var moduleInstance = ReflectionUtils.CreateObject(typeInfo, this, dependencyMap);
modules.Add(LoadInternal(moduleInstance, moduleAttr, typeInfo, dependencyMap));
var typeInfo = type.GetTypeInfo();
if (_moduleTypeInfo.IsAssignableFrom(typeInfo))
{
var dontAutoLoad = typeInfo.GetCustomAttribute<DontAutoLoadAttribute>();
if (dontAutoLoad == null)
moduleDefs.Add(AddModuleInternal(typeInfo, dependencyMap));
}
} }
} }
return modules.ToImmutable();
return moduleDefs.ToImmutable();
} }
finally finally
{ {
_moduleLock.Release(); _moduleLock.Release();
} }
} }
private ModuleInfo AddModuleInternal(TypeInfo typeInfo, IDependencyMap dependencyMap)
{
var moduleDef = new ModuleInfo(typeInfo, this, dependencyMap);
_moduleDefs[typeInfo.BaseType] = moduleDef;

foreach (var cmd in moduleDef.Commands)
_map.AddCommand(cmd);

return moduleDef;
}


public async Task<bool> Unload(Module module)
public async Task<bool> RemoveModule(ModuleInfo module)
{ {
await _moduleLock.WaitAsync().ConfigureAwait(false); await _moduleLock.WaitAsync().ConfigureAwait(false);
try try
{ {
return UnloadInternal(module.Instance);
return RemoveModuleInternal(module.Source.BaseType);
} }
finally finally
{ {
_moduleLock.Release(); _moduleLock.Release();
} }
} }
public async Task<bool> Unload(object moduleInstance)
public async Task<bool> RemoveModule<T>()
{ {
await _moduleLock.WaitAsync().ConfigureAwait(false); await _moduleLock.WaitAsync().ConfigureAwait(false);
try try
{ {
return UnloadInternal(moduleInstance);
return RemoveModuleInternal(typeof(T));
} }
finally finally
{ {
_moduleLock.Release(); _moduleLock.Release();
} }
} }
private bool UnloadInternal(object module)
private bool RemoveModuleInternal(Type type)
{ {
Module unloadedModule;
if (_modules.TryRemove(module.GetType(), out unloadedModule))
ModuleInfo unloadedModule;
if (_moduleDefs.TryRemove(type, out unloadedModule))
{ {
foreach (var cmd in unloadedModule.Commands) foreach (var cmd in unloadedModule.Commands)
_map.RemoveCommand(cmd); _map.RemoveCommand(cmd);
@@ -178,6 +158,24 @@ namespace Discord.Commands
return false; return false;
} }


//Type Readers
public void AddTypeReader<T>(TypeReader reader)
{
_typeReaders[typeof(T)] = reader;
}
public void AddTypeReader(Type type, TypeReader reader)
{
_typeReaders[type] = reader;
}
internal TypeReader GetTypeReader(Type type)
{
TypeReader reader;
if (_typeReaders.TryGetValue(type, out reader))
return reader;
return null;
}

//Execution
public SearchResult Search(CommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos)); public SearchResult Search(CommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos));
public SearchResult Search(CommandContext context, string input) public SearchResult Search(CommandContext context, string input)
{ {


+ 4
- 4
src/Discord.Net.Commands/Map/CommandMap.cs View File

@@ -16,7 +16,7 @@ namespace Discord.Commands
_nodes = new ConcurrentDictionary<string, CommandMapNode>(); _nodes = new ConcurrentDictionary<string, CommandMapNode>();
} }


public void AddCommand(Command command)
public void AddCommand(CommandInfo command)
{ {
foreach (string text in command.Aliases) foreach (string text in command.Aliases)
{ {
@@ -35,7 +35,7 @@ namespace Discord.Commands
} }
} }
} }
public void RemoveCommand(Command command)
public void RemoveCommand(CommandInfo command)
{ {
foreach (string text in command.Aliases) foreach (string text in command.Aliases)
{ {
@@ -60,7 +60,7 @@ namespace Discord.Commands
} }
} }


public IEnumerable<Command> GetCommands(string text)
public IEnumerable<CommandInfo> GetCommands(string text)
{ {
int nextSpace = NextWhitespace(text); int nextSpace = NextWhitespace(text);
string name; string name;
@@ -76,7 +76,7 @@ namespace Discord.Commands
if (_nodes.TryGetValue(name, out nextNode)) if (_nodes.TryGetValue(name, out nextNode))
return nextNode.GetCommands(text, nextSpace + 1); return nextNode.GetCommands(text, nextSpace + 1);
else else
return Enumerable.Empty<Command>();
return Enumerable.Empty<CommandInfo>();
} }
} }




+ 5
- 5
src/Discord.Net.Commands/Map/CommandMapNode.cs View File

@@ -9,7 +9,7 @@ namespace Discord.Commands
private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; private readonly ConcurrentDictionary<string, CommandMapNode> _nodes;
private readonly string _name; private readonly string _name;
private readonly object _lockObj = new object(); private readonly object _lockObj = new object();
private ImmutableArray<Command> _commands;
private ImmutableArray<CommandInfo> _commands;


public bool IsEmpty => _commands.Length == 0 && _nodes.Count == 0; public bool IsEmpty => _commands.Length == 0 && _nodes.Count == 0;


@@ -17,10 +17,10 @@ namespace Discord.Commands
{ {
_name = name; _name = name;
_nodes = new ConcurrentDictionary<string, CommandMapNode>(); _nodes = new ConcurrentDictionary<string, CommandMapNode>();
_commands = ImmutableArray.Create<Command>();
_commands = ImmutableArray.Create<CommandInfo>();
} }


public void AddCommand(string text, int index, Command command)
public void AddCommand(string text, int index, CommandInfo command)
{ {
int nextSpace = text.IndexOf(' ', index); int nextSpace = text.IndexOf(' ', index);
string name; string name;
@@ -41,7 +41,7 @@ namespace Discord.Commands
} }
} }
} }
public void RemoveCommand(string text, int index, Command command)
public void RemoveCommand(string text, int index, CommandInfo command)
{ {
int nextSpace = text.IndexOf(' ', index); int nextSpace = text.IndexOf(' ', index);
string name; string name;
@@ -68,7 +68,7 @@ namespace Discord.Commands
} }
} }


public IEnumerable<Command> GetCommands(string text, int index)
public IEnumerable<CommandInfo> GetCommands(string text, int index)
{ {
int nextSpace = text.IndexOf(' ', index); int nextSpace = text.IndexOf(' ', index);
string name; string name;


+ 15
- 0
src/Discord.Net.Commands/ModuleBase.cs View File

@@ -0,0 +1,15 @@
using System.Threading.Tasks;

namespace Discord.Commands
{
public abstract class ModuleBase
{
public IDiscordClient Client { get; internal set; }
public CommandContext Context { get; internal set; }

protected virtual async Task ReplyAsync(string message, bool isTTS = false, RequestOptions options = null)
{
await Context.Channel.SendMessageAsync(message, isTTS, options).ConfigureAwait(false);
}
}
}

src/Discord.Net.Commands/Module.cs → src/Discord.Net.Commands/ModuleInfo.cs View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
@@ -6,26 +7,31 @@ using System.Reflection;
namespace Discord.Commands namespace Discord.Commands
{ {
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class Module
public class ModuleInfo
{ {
internal readonly Func<ModuleBase> _builder;

public TypeInfo Source { get; } public TypeInfo Source { get; }
public CommandService Service { get; } public CommandService Service { get; }
public string Name { get; } public string Name { get; }
public string Prefix { get; } public string Prefix { get; }
public string Summary { get; } public string Summary { get; }
public string Remarks { get; } public string Remarks { get; }
public IEnumerable<Command> Commands { get; }
internal object Instance { get; }

public IEnumerable<CommandInfo> Commands { get; }
public IReadOnlyList<PreconditionAttribute> Preconditions { get; } public IReadOnlyList<PreconditionAttribute> Preconditions { get; }


internal Module(TypeInfo source, CommandService service, object instance, ModuleAttribute moduleAttr, IDependencyMap dependencyMap)
internal ModuleInfo(TypeInfo source, CommandService service, IDependencyMap dependencyMap)
{ {
Source = source; Source = source;
Service = service; Service = service;
Name = source.Name; Name = source.Name;
Prefix = moduleAttr.Prefix ?? "";
Instance = instance;
_builder = ReflectionUtils.CreateBuilder<ModuleBase>(source, Service, dependencyMap);

var groupAttr = source.GetCustomAttribute<GroupAttribute>();
if (groupAttr != null)
Prefix = groupAttr.Prefix;
else
Prefix = "";


var nameAttr = source.GetCustomAttribute<NameAttribute>(); var nameAttr = source.GetCustomAttribute<NameAttribute>();
if (nameAttr != null) if (nameAttr != null)
@@ -39,20 +45,19 @@ namespace Discord.Commands
if (remarksAttr != null) if (remarksAttr != null)
Remarks = remarksAttr.Text; Remarks = remarksAttr.Text;


List<Command> commands = new List<Command>();
SearchClass(source, instance, commands, Prefix, dependencyMap);
List<CommandInfo> commands = new List<CommandInfo>();
SearchClass(source, commands, Prefix, dependencyMap);
Commands = commands; Commands = commands;


Preconditions = BuildPreconditions();
Preconditions = Source.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray();
} }

private void SearchClass(TypeInfo parentType, object instance, List<Command> commands, string groupPrefix, IDependencyMap dependencyMap)
private void SearchClass(TypeInfo parentType, List<CommandInfo> commands, string groupPrefix, IDependencyMap dependencyMap)
{ {
foreach (var method in parentType.DeclaredMethods) foreach (var method in parentType.DeclaredMethods)
{ {
var cmdAttr = method.GetCustomAttribute<CommandAttribute>(); var cmdAttr = method.GetCustomAttribute<CommandAttribute>();
if (cmdAttr != null) if (cmdAttr != null)
commands.Add(new Command(method, this, instance, cmdAttr, groupPrefix));
commands.Add(new CommandInfo(method, this, cmdAttr, groupPrefix));
} }
foreach (var type in parentType.DeclaredNestedTypes) foreach (var type in parentType.DeclaredNestedTypes)
{ {
@@ -66,15 +71,13 @@ namespace Discord.Commands
else else
nextGroupPrefix = groupAttrib.Prefix ?? type.Name.ToLowerInvariant(); nextGroupPrefix = groupAttrib.Prefix ?? type.Name.ToLowerInvariant();


SearchClass(type, ReflectionUtils.CreateObject(type, Service, dependencyMap), commands, nextGroupPrefix, dependencyMap);
SearchClass(type, commands, nextGroupPrefix, dependencyMap);
} }
} }
} }


private IReadOnlyList<PreconditionAttribute> BuildPreconditions()
{
return Source.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray();
}
internal ModuleBase CreateInstance()
=> _builder();


public override string ToString() => Name; public override string ToString() => Name;
private string DebuggerDisplay => Name; private string DebuggerDisplay => Name;

+ 15
- 9
src/Discord.Net.Commands/ReflectionUtils.cs View File

@@ -6,7 +6,10 @@ namespace Discord.Commands
{ {
internal class ReflectionUtils internal class ReflectionUtils
{ {
internal static object CreateObject(TypeInfo typeInfo, CommandService service, IDependencyMap map = null)
internal static T CreateObject<T>(TypeInfo typeInfo, CommandService service, IDependencyMap map = null)
=> CreateBuilder<T>(typeInfo, service, map)();

internal static Func<T> CreateBuilder<T>(TypeInfo typeInfo, CommandService service, IDependencyMap map = null)
{ {
var constructors = typeInfo.DeclaredConstructors.Where(x => !x.IsStatic).ToArray(); var constructors = typeInfo.DeclaredConstructors.Where(x => !x.IsStatic).ToArray();
if (constructors.Length == 0) if (constructors.Length == 0)
@@ -14,7 +17,7 @@ namespace Discord.Commands
else if (constructors.Length > 1) else if (constructors.Length > 1)
throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\""); throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\"");


var constructor = constructors[0];
var constructor = constructors[0];
ParameterInfo[] parameters = constructor.GetParameters(); ParameterInfo[] parameters = constructor.GetParameters();
object[] args = new object[parameters.Length]; object[] args = new object[parameters.Length];


@@ -34,14 +37,17 @@ namespace Discord.Commands
args[i] = arg; args[i] = arg;
} }


try
{
return constructor.Invoke(args);
}
catch (Exception ex)
return () =>
{ {
throw new Exception($"Failed to create \"{typeInfo.FullName}\"", ex);
}
try
{
return (T)constructor.Invoke(args);
}
catch (Exception ex)
{
throw new Exception($"Failed to create \"{typeInfo.FullName}\"", ex);
}
};
} }
} }
} }

+ 3
- 3
src/Discord.Net.Commands/Results/SearchResult.cs View File

@@ -7,14 +7,14 @@ namespace Discord.Commands
public struct SearchResult : IResult public struct SearchResult : IResult
{ {
public string Text { get; } public string Text { get; }
public IReadOnlyList<Command> Commands { get; }
public IReadOnlyList<CommandInfo> Commands { get; }


public CommandError? Error { get; } public CommandError? Error { get; }
public string ErrorReason { get; } public string ErrorReason { get; }


public bool IsSuccess => !Error.HasValue; public bool IsSuccess => !Error.HasValue;


private SearchResult(string text, IReadOnlyList<Command> commands, CommandError? error, string errorReason)
private SearchResult(string text, IReadOnlyList<CommandInfo> commands, CommandError? error, string errorReason)
{ {
Text = text; Text = text;
Commands = commands; Commands = commands;
@@ -22,7 +22,7 @@ namespace Discord.Commands
ErrorReason = errorReason; ErrorReason = errorReason;
} }


public static SearchResult FromSuccess(string text, IReadOnlyList<Command> commands)
public static SearchResult FromSuccess(string text, IReadOnlyList<CommandInfo> commands)
=> new SearchResult(text, commands, null, null); => new SearchResult(text, commands, null, null);
public static SearchResult FromError(CommandError error, string reason) public static SearchResult FromError(CommandError error, string reason)
=> new SearchResult(null, null, error, reason); => new SearchResult(null, null, error, reason);


+ 1
- 1
src/Discord.Net.Core/Utils/ConcurrentHashSet.cs View File

@@ -9,7 +9,7 @@ namespace Discord
{ {
//Based on https://github.com/dotnet/corefx/blob/d0dc5fc099946adc1035b34a8b1f6042eddb0c75/src/System.Threading.Tasks.Parallel/src/System/Threading/PlatformHelper.cs //Based on https://github.com/dotnet/corefx/blob/d0dc5fc099946adc1035b34a8b1f6042eddb0c75/src/System.Threading.Tasks.Parallel/src/System/Threading/PlatformHelper.cs
//Copyright (c) .NET Foundation and Contributors //Copyright (c) .NET Foundation and Contributors
public static class ConcurrentHashSet
internal static class ConcurrentHashSet
{ {
private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000; private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000;
private static volatile int s_processorCount; private static volatile int s_processorCount;


Loading…
Cancel
Save