@@ -0,0 +1,9 @@ | |||
using System; | |||
namespace Discord.Commands | |||
{ | |||
[AttributeUsage(AttributeTargets.Class)] | |||
public class DontAutoLoadAttribute : Attribute | |||
{ | |||
} | |||
} |
@@ -1,8 +0,0 @@ | |||
using System; | |||
namespace Discord.Commands | |||
{ | |||
public class InAttribute : Attribute | |||
{ | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -6,6 +6,6 @@ namespace Discord.Commands | |||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] | |||
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); | |||
} | |||
} |
@@ -21,7 +21,7 @@ namespace Discord.Commands | |||
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; | |||
@@ -20,7 +20,7 @@ namespace Discord.Commands | |||
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; | |||
@@ -10,16 +10,15 @@ using System.Threading.Tasks; | |||
namespace Discord.Commands | |||
{ | |||
[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 readonly object _instance; | |||
private readonly Func<CommandContext, IReadOnlyList<object>, Task> _action; | |||
private readonly Func<CommandContext, object[], Task> _action; | |||
public MethodInfo Source { get; } | |||
public Module Module { get; } | |||
public ModuleInfo Module { get; } | |||
public string Name { get; } | |||
public string Summary { get; } | |||
public string Remarks { get; } | |||
@@ -30,13 +29,12 @@ namespace Discord.Commands | |||
public IReadOnlyList<CommandParameter> Parameters { 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 | |||
{ | |||
Source = source; | |||
Module = module; | |||
_instance = instance; | |||
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) | |||
{ | |||
var result = await precondition.CheckPermissions(context, this, Module.Instance).ConfigureAwait(false); | |||
var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); | |||
if (!result.IsSuccess) | |||
return result; | |||
} | |||
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) | |||
return result; | |||
} | |||
@@ -169,11 +167,9 @@ namespace Discord.Commands | |||
private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo) | |||
{ | |||
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 type = parameter.ParameterType; | |||
@@ -209,19 +205,23 @@ namespace Discord.Commands | |||
} | |||
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)) | |||
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(); | |||
} | |||
}; | |||
} | |||
@@ -13,7 +13,7 @@ namespace Discord.Commands | |||
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; | |||
StringBuilder argBuilder = new StringBuilder(input.Length); | |||
@@ -11,18 +11,20 @@ namespace Discord.Commands | |||
{ | |||
public class CommandService | |||
{ | |||
private static readonly TypeInfo _moduleTypeInfo = typeof(ModuleBase).GetTypeInfo(); | |||
private readonly SemaphoreSlim _moduleLock; | |||
private readonly ConcurrentDictionary<Type, Module> _modules; | |||
private readonly ConcurrentDictionary<Type, ModuleInfo> _moduleDefs; | |||
private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders; | |||
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() | |||
{ | |||
_moduleLock = new SemaphoreSlim(1, 1); | |||
_modules = new ConcurrentDictionary<Type, Module>(); | |||
_moduleDefs = new ConcurrentDictionary<Type, ModuleInfo>(); | |||
_map = new CommandMap(); | |||
_typeReaders = new ConcurrentDictionary<Type, TypeReader> | |||
{ | |||
@@ -45,7 +47,6 @@ namespace Discord.Commands | |||
[typeof(IMessage)] = new MessageTypeReader<IMessage>(), | |||
[typeof(IUserMessage)] = new MessageTypeReader<IUserMessage>(), | |||
//[typeof(ISystemMessage)] = new MessageTypeReader<ISystemMessage>(), | |||
[typeof(IChannel)] = new ChannelTypeReader<IChannel>(), | |||
[typeof(IDMChannel)] = new ChannelTypeReader<IDMChannel>(), | |||
[typeof(IGroupChannel)] = new ChannelTypeReader<IGroupChannel>(), | |||
@@ -55,120 +56,99 @@ namespace Discord.Commands | |||
[typeof(ITextChannel)] = new ChannelTypeReader<ITextChannel>(), | |||
[typeof(IVoiceChannel)] = new ChannelTypeReader<IVoiceChannel>(), | |||
//[typeof(IGuild)] = new GuildTypeReader<IGuild>(), | |||
[typeof(IRole)] = new RoleTypeReader<IRole>(), | |||
//[typeof(IInvite)] = new InviteTypeReader<IInvite>(), | |||
//[typeof(IInviteMetadata)] = new InviteTypeReader<IInviteMetadata>(), | |||
[typeof(IUser)] = new UserTypeReader<IUser>(), | |||
[typeof(IGroupUser)] = new UserTypeReader<IGroupUser>(), | |||
[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); | |||
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 | |||
{ | |||
_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); | |||
try | |||
{ | |||
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 | |||
{ | |||
_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); | |||
try | |||
{ | |||
return UnloadInternal(module.Instance); | |||
return RemoveModuleInternal(module.Source.BaseType); | |||
} | |||
finally | |||
{ | |||
_moduleLock.Release(); | |||
} | |||
} | |||
public async Task<bool> Unload(object moduleInstance) | |||
public async Task<bool> RemoveModule<T>() | |||
{ | |||
await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
return UnloadInternal(moduleInstance); | |||
return RemoveModuleInternal(typeof(T)); | |||
} | |||
finally | |||
{ | |||
_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) | |||
_map.RemoveCommand(cmd); | |||
@@ -178,6 +158,24 @@ namespace Discord.Commands | |||
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, string input) | |||
{ | |||
@@ -16,7 +16,7 @@ namespace Discord.Commands | |||
_nodes = new ConcurrentDictionary<string, CommandMapNode>(); | |||
} | |||
public void AddCommand(Command command) | |||
public void AddCommand(CommandInfo command) | |||
{ | |||
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) | |||
{ | |||
@@ -60,7 +60,7 @@ namespace Discord.Commands | |||
} | |||
} | |||
public IEnumerable<Command> GetCommands(string text) | |||
public IEnumerable<CommandInfo> GetCommands(string text) | |||
{ | |||
int nextSpace = NextWhitespace(text); | |||
string name; | |||
@@ -76,7 +76,7 @@ namespace Discord.Commands | |||
if (_nodes.TryGetValue(name, out nextNode)) | |||
return nextNode.GetCommands(text, nextSpace + 1); | |||
else | |||
return Enumerable.Empty<Command>(); | |||
return Enumerable.Empty<CommandInfo>(); | |||
} | |||
} | |||
@@ -9,7 +9,7 @@ namespace Discord.Commands | |||
private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | |||
private readonly string _name; | |||
private readonly object _lockObj = new object(); | |||
private ImmutableArray<Command> _commands; | |||
private ImmutableArray<CommandInfo> _commands; | |||
public bool IsEmpty => _commands.Length == 0 && _nodes.Count == 0; | |||
@@ -17,10 +17,10 @@ namespace Discord.Commands | |||
{ | |||
_name = name; | |||
_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); | |||
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); | |||
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); | |||
string name; | |||
@@ -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); | |||
} | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
using System.Collections.Generic; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Diagnostics; | |||
using System.Reflection; | |||
@@ -6,26 +7,31 @@ using System.Reflection; | |||
namespace Discord.Commands | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class Module | |||
public class ModuleInfo | |||
{ | |||
internal readonly Func<ModuleBase> _builder; | |||
public TypeInfo Source { get; } | |||
public CommandService Service { get; } | |||
public string Name { get; } | |||
public string Prefix { get; } | |||
public string Summary { get; } | |||
public string Remarks { get; } | |||
public IEnumerable<Command> Commands { get; } | |||
internal object Instance { get; } | |||
public IEnumerable<CommandInfo> Commands { 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; | |||
Service = service; | |||
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>(); | |||
if (nameAttr != null) | |||
@@ -39,20 +45,19 @@ namespace Discord.Commands | |||
if (remarksAttr != null) | |||
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; | |||
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) | |||
{ | |||
var cmdAttr = method.GetCustomAttribute<CommandAttribute>(); | |||
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) | |||
{ | |||
@@ -66,15 +71,13 @@ namespace Discord.Commands | |||
else | |||
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; | |||
private string DebuggerDisplay => Name; |
@@ -6,7 +6,10 @@ namespace Discord.Commands | |||
{ | |||
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(); | |||
if (constructors.Length == 0) | |||
@@ -14,7 +17,7 @@ namespace Discord.Commands | |||
else if (constructors.Length > 1) | |||
throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\""); | |||
var constructor = constructors[0]; | |||
var constructor = constructors[0]; | |||
ParameterInfo[] parameters = constructor.GetParameters(); | |||
object[] args = new object[parameters.Length]; | |||
@@ -34,14 +37,17 @@ namespace Discord.Commands | |||
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); | |||
} | |||
}; | |||
} | |||
} | |||
} |
@@ -7,14 +7,14 @@ namespace Discord.Commands | |||
public struct SearchResult : IResult | |||
{ | |||
public string Text { get; } | |||
public IReadOnlyList<Command> Commands { get; } | |||
public IReadOnlyList<CommandInfo> Commands { get; } | |||
public CommandError? Error { get; } | |||
public string ErrorReason { get; } | |||
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; | |||
Commands = commands; | |||
@@ -22,7 +22,7 @@ namespace Discord.Commands | |||
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); | |||
public static SearchResult FromError(CommandError error, string reason) | |||
=> new SearchResult(null, null, error, reason); | |||
@@ -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 | |||
//Copyright (c) .NET Foundation and Contributors | |||
public static class ConcurrentHashSet | |||
internal static class ConcurrentHashSet | |||
{ | |||
private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000; | |||
private static volatile int s_processorCount; | |||