From b7a5ee6542477d3e3b1a73443ba55b1f033e7aea Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sat, 19 Nov 2016 15:12:04 +0000 Subject: [PATCH 1/6] Parameter preconditions and typereader overriding --- .../Attributes/OverrideTypeReaderAttribute.cs | 22 ++++++++++ .../ParameterPreconditionAttribute.cs | 11 +++++ .../Builders/ModuleClassBuilder.cs | 4 ++ .../Builders/ParameterBuilder.cs | 18 +++++++- src/Discord.Net.Commands/Info/CommandInfo.cs | 19 ++++++++- .../Info/ParameterInfo.cs | 42 +++++++++++++++---- 6 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs create mode 100644 src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs diff --git a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs new file mode 100644 index 00000000..e14be063 --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs @@ -0,0 +1,22 @@ +using System; + +using System.Reflection; + +namespace Discord.Commands +{ + [AttributeUsage(AttributeTargets.Parameter)] + public class OverrideTypeReaderAttribute : Attribute + { + private readonly TypeInfo _typeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); + + public Type TypeReader { get; } + + public OverrideTypeReaderAttribute(Type overridenType) + { + if (!_typeReaderTypeInfo.IsAssignableFrom(overridenType.GetTypeInfo())) + throw new ArgumentException($"{nameof(overridenType)} must inherit from {nameof(TypeReader)}"); + + TypeReader = overridenType; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs new file mode 100644 index 00000000..f2ef78c0 --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] + public abstract class ParameterPreconditionAttribute : Attribute + { + public abstract Task CheckPermissions(CommandContext context, ParameterInfo parameter, object value); + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index aaec4316..1775cc1f 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -182,6 +182,10 @@ namespace Discord.Commands // TODO: C#7 type switch if (attribute is SummaryAttribute) builder.Summary = (attribute as SummaryAttribute).Text; + else if (attribute is OverrideTypeReaderAttribute) + builder.TypeReader = service.GetTypeReader((attribute as OverrideTypeReaderAttribute).TypeReader); + else if (attribute is ParameterPreconditionAttribute) + builder.AddPrecondition(attribute as ParameterPreconditionAttribute); else if (attribute is ParamArrayAttribute) { builder.IsMultiple = true; diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index 801a1008..6b941a1c 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -1,10 +1,14 @@ using System; using System.Reflection; +using System.Collections.Generic; + namespace Discord.Commands.Builders { public class ParameterBuilder { + private readonly List _preconditions; + public CommandBuilder Command { get; } public string Name { get; internal set; } public Type ParameterType { get; internal set; } @@ -16,16 +20,20 @@ namespace Discord.Commands.Builders public object DefaultValue { get; set; } public string Summary { get; set; } + public IReadOnlyList Preconditions => _preconditions; + //Automatic internal ParameterBuilder(CommandBuilder command) { + _preconditions = new List(); + Command = command; } //User-defined internal ParameterBuilder(CommandBuilder command, string name, Type type) : this(command) { - Preconditions.NotNull(name, nameof(name)); + Discord.Preconditions.NotNull(name, nameof(name)); Name = name; SetType(type); @@ -49,7 +57,7 @@ namespace Discord.Commands.Builders } public ParameterBuilder WithDefault(object defaultValue) { - DefaultValue = defaultValue; + DefaultValue = defaultValue; return this; } public ParameterBuilder WithIsOptional(bool isOptional) @@ -68,6 +76,12 @@ namespace Discord.Commands.Builders return this; } + public ParameterBuilder AddPrecondition(ParameterPreconditionAttribute precondition) + { + _preconditions.Add(precondition); + return this; + } + internal ParameterInfo Build(CommandInfo info) { if (TypeReader == null) diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 571c47e1..a91a1f9f 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -135,9 +135,26 @@ namespace Discord.Commands if (map == null) map = DependencyMap.Empty; + object[] args = null; + + try + { + args = GenerateArgs(argList, paramList); + } + catch (Exception ex) + { + return ExecuteResult.FromError(ex); + } + + foreach (var parameter in Parameters) + { + var result = await parameter.CheckPreconditionsAsync(context, args, map).ConfigureAwait(false); + if (!result.IsSuccess) + return ExecuteResult.FromError(result); + } + try { - var args = GenerateArgs(argList, paramList); switch (RunMode) { case RunMode.Sync: //Always sync diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index 18c5e653..2ef4b89c 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -1,5 +1,7 @@ using System; using System.Linq; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading.Tasks; using Discord.Commands.Builders; @@ -10,6 +12,17 @@ namespace Discord.Commands { private readonly TypeReader _reader; + public CommandInfo Command { get; } + public string Name { get; } + public string Summary { get; } + public bool IsOptional { get; } + public bool IsRemainder { get; } + public bool IsMultiple { get; } + public Type Type { get; } + public object DefaultValue { get; } + + public IReadOnlyList Preconditions { get; } + internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service) { Command = command; @@ -23,17 +36,30 @@ namespace Discord.Commands Type = builder.ParameterType; DefaultValue = builder.DefaultValue; + Preconditions = builder.Preconditions.ToImmutableArray(); + _reader = builder.TypeReader; } - public CommandInfo Command { get; } - public string Name { get; } - public string Summary { get; } - public bool IsOptional { get; } - public bool IsRemainder { get; } - public bool IsMultiple { get; } - public Type Type { get; } - public object DefaultValue { get; } + public async Task CheckPreconditionsAsync(CommandContext context, object[] args, IDependencyMap map = null) + { + if (map == null) + map = DependencyMap.Empty; + + int position = 0; + for(position = 0; position < Command.Parameters.Count; position++) + if (Command.Parameters[position] == this) + break; + + foreach (var precondition in Preconditions) + { + var result = await precondition.CheckPermissions(context, this, args[position]).ConfigureAwait(false); + if (!result.IsSuccess) + return result; + } + + return PreconditionResult.FromSuccess(); + } public async Task Parse(CommandContext context, string input) { From f11f416024007f8f5dfa665167c142807506dee3 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sat, 19 Nov 2016 21:09:49 +0000 Subject: [PATCH 2/6] Add IDependencyMap to parameter preconditions --- .../Attributes/ParameterPreconditionAttribute.cs | 2 +- src/Discord.Net.Commands/Info/ParameterInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs index f2ef78c0..3bf8d177 100644 --- a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs @@ -6,6 +6,6 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] public abstract class ParameterPreconditionAttribute : Attribute { - public abstract Task CheckPermissions(CommandContext context, ParameterInfo parameter, object value); + public abstract Task CheckPermissions(CommandContext context, ParameterInfo parameter, object value, IDependencyMap map); } } \ No newline at end of file diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index 2ef4b89c..f8a97647 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -53,7 +53,7 @@ namespace Discord.Commands foreach (var precondition in Preconditions) { - var result = await precondition.CheckPermissions(context, this, args[position]).ConfigureAwait(false); + var result = await precondition.CheckPermissions(context, this, args[position], map).ConfigureAwait(false); if (!result.IsSuccess) return result; } From 156483bf717ca9d11d9331c08e4f32511b25a2a6 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sat, 19 Nov 2016 21:11:30 +0000 Subject: [PATCH 3/6] Rename `overridenType` to `overridenTypeReader` The previous name was causing some confusion --- .../Attributes/OverrideTypeReaderAttribute.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs index e14be063..8134b06b 100644 --- a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs @@ -11,12 +11,12 @@ namespace Discord.Commands public Type TypeReader { get; } - public OverrideTypeReaderAttribute(Type overridenType) + public OverrideTypeReaderAttribute(Type overridenTypeReader) { - if (!_typeReaderTypeInfo.IsAssignableFrom(overridenType.GetTypeInfo())) - throw new ArgumentException($"{nameof(overridenType)} must inherit from {nameof(TypeReader)}"); + if (!_typeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) + throw new ArgumentException($"{nameof(overridenTypeReader)} must inherit from {nameof(TypeReader)}"); - TypeReader = overridenType; + TypeReader = overridenTypeReader; } } } \ No newline at end of file From d2d7b4dce7bb578eefbf078428f1ae0b34f33437 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sat, 19 Nov 2016 21:16:28 +0000 Subject: [PATCH 4/6] Make `_typeReaderTypeInfo` static Seems I missed this originally, whoops. --- .../Attributes/OverrideTypeReaderAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs index 8134b06b..37f685c9 100644 --- a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs @@ -7,7 +7,7 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Parameter)] public class OverrideTypeReaderAttribute : Attribute { - private readonly TypeInfo _typeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); + private static readonly TypeInfo _typeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); public Type TypeReader { get; } From 254e874c999336179dd878dd733cbcb53820cc03 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Mon, 21 Nov 2016 18:46:21 +0000 Subject: [PATCH 5/6] Fix OverrideTypeReader This commit also adds a TypeReaders property to CommandService, so it is possible to see all of the registered TypeReaders. This makes it possible for users to implement their own parsing instead of using the built-in parsing. --- .../Builders/ModuleClassBuilder.cs | 48 ++++++++--- .../Builders/ParameterBuilder.cs | 7 +- src/Discord.Net.Commands/CommandService.cs | 83 ++++++++++--------- 3 files changed, 86 insertions(+), 52 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 1775cc1f..aaa96fb8 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -183,7 +183,9 @@ namespace Discord.Commands if (attribute is SummaryAttribute) builder.Summary = (attribute as SummaryAttribute).Text; else if (attribute is OverrideTypeReaderAttribute) - builder.TypeReader = service.GetTypeReader((attribute as OverrideTypeReaderAttribute).TypeReader); + { + builder.TypeReader = GetTypeReader(service, paramType, (attribute as OverrideTypeReaderAttribute).TypeReader); + } else if (attribute is ParameterPreconditionAttribute) builder.AddPrecondition(attribute as ParameterPreconditionAttribute); else if (attribute is ParamArrayAttribute) @@ -200,23 +202,47 @@ namespace Discord.Commands } } - var reader = service.GetTypeReader(paramType); - if (reader == null) + if (builder.TypeReader == null) { - var paramTypeInfo = paramType.GetTypeInfo(); - if (paramTypeInfo.IsEnum) + var readers = service.GetTypeReaders(paramType); + var reader = readers?.FirstOrDefault(); + + if (reader == null) { - reader = EnumTypeReader.GetReader(paramType); - service.AddTypeReader(paramType, reader); + var paramTypeInfo = paramType.GetTypeInfo(); + if (paramTypeInfo.IsEnum) + { + reader = EnumTypeReader.GetReader(paramType); + service.AddTypeReader(paramType, reader); + } + else + { + throw new InvalidOperationException($"{paramType.FullName} is not supported as a command parameter, are you missing a TypeReader?"); + } } - else + + builder.ParameterType = paramType; + builder.TypeReader = reader; + } + } + + private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType) + { + var readers = service.GetTypeReaders(paramType); + if (readers != null) + { + var reader = readers.FirstOrDefault(x => x.GetType() == typeReaderType); + if (reader != default(TypeReader)) { - throw new InvalidOperationException($"{paramType.FullName} is not supported as a command parameter, are you missing a TypeReader?"); + return reader; } } - builder.ParameterType = paramType; - builder.TypeReader = reader; + //could not find any registered type reader: try to create one + var typeReader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, DependencyMap.Empty); + service.AddTypeReader(paramType, typeReader); + + return typeReader; } private static bool IsValidModuleDefinition(TypeInfo typeInfo) diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index 6b941a1c..89f89b3c 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Reflection; using System.Collections.Generic; @@ -41,7 +42,11 @@ namespace Discord.Commands.Builders internal void SetType(Type type) { - TypeReader = Command.Module.Service.GetTypeReader(type); + var readers = Command.Module.Service.GetTypeReaders(type); + if (readers == null) + throw new InvalidOperationException($"{type} does not have a TypeReader registered for it"); + + TypeReader = readers.FirstOrDefault(); if (type.GetTypeInfo().IsValueType) DefaultValue = Activator.CreateInstance(type); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index b6659fea..285a3543 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -14,8 +14,8 @@ namespace Discord.Commands public class CommandService { private readonly SemaphoreSlim _moduleLock; - private readonly ConcurrentDictionary _typedModuleDefs; - private readonly ConcurrentDictionary _typeReaders; + private readonly ConcurrentDictionary _typedModuleDefs; + private readonly ConcurrentDictionary> _typeReaders; private readonly ConcurrentBag _moduleDefs; private readonly CommandMap _map; @@ -24,6 +24,7 @@ namespace Discord.Commands public IEnumerable Modules => _moduleDefs.Select(x => x); public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Commands); + public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value, (a, value) => new {a.Key, value}).ToLookup(x => x.Key, x => x.value); public CommandService() : this(new CommandServiceConfig()) { } public CommandService(CommandServiceConfig config) @@ -32,41 +33,41 @@ namespace Discord.Commands _typedModuleDefs = new ConcurrentDictionary(); _moduleDefs = new ConcurrentBag(); _map = new CommandMap(); - _typeReaders = new ConcurrentDictionary + _typeReaders = new ConcurrentDictionary> { - [typeof(bool)] = new SimpleTypeReader(), - [typeof(char)] = new SimpleTypeReader(), - [typeof(string)] = new SimpleTypeReader(), - [typeof(byte)] = new SimpleTypeReader(), - [typeof(sbyte)] = new SimpleTypeReader(), - [typeof(ushort)] = new SimpleTypeReader(), - [typeof(short)] = new SimpleTypeReader(), - [typeof(uint)] = new SimpleTypeReader(), - [typeof(int)] = new SimpleTypeReader(), - [typeof(ulong)] = new SimpleTypeReader(), - [typeof(long)] = new SimpleTypeReader(), - [typeof(float)] = new SimpleTypeReader(), - [typeof(double)] = new SimpleTypeReader(), - [typeof(decimal)] = new SimpleTypeReader(), - [typeof(DateTime)] = new SimpleTypeReader(), - [typeof(DateTimeOffset)] = new SimpleTypeReader(), - [typeof(TimeSpan)] = new SimpleTypeReader(), - [typeof(IMessage)] = new MessageTypeReader(), - [typeof(IUserMessage)] = new MessageTypeReader(), - [typeof(IChannel)] = new ChannelTypeReader(), - [typeof(IDMChannel)] = new ChannelTypeReader(), - [typeof(IGroupChannel)] = new ChannelTypeReader(), - [typeof(IGuildChannel)] = new ChannelTypeReader(), - [typeof(IMessageChannel)] = new ChannelTypeReader(), - [typeof(IPrivateChannel)] = new ChannelTypeReader(), - [typeof(ITextChannel)] = new ChannelTypeReader(), - [typeof(IVoiceChannel)] = new ChannelTypeReader(), + [typeof(bool)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(char)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(string)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(byte)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(sbyte)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(ushort)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(short)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(uint)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(int)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(ulong)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(long)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(float)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(double)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(decimal)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(DateTime)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(DateTimeOffset)] = new ConcurrentBag{new SimpleTypeReader()}, + + [typeof(IMessage)] = new ConcurrentBag{new MessageTypeReader()}, + [typeof(IUserMessage)] = new ConcurrentBag{new MessageTypeReader()}, + [typeof(IChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IDMChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IGroupChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IGuildChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IMessageChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IPrivateChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(ITextChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IVoiceChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IRole)] = new RoleTypeReader(), + [typeof(IRole)] = new ConcurrentBag{new RoleTypeReader()}, - [typeof(IUser)] = new UserTypeReader(), - [typeof(IGroupUser)] = new UserTypeReader(), - [typeof(IGuildUser)] = new UserTypeReader(), + [typeof(IUser)] = new ConcurrentBag{new UserTypeReader()}, + [typeof(IGroupUser)] = new ConcurrentBag{new UserTypeReader()}, + [typeof(IGuildUser)] = new ConcurrentBag{new UserTypeReader()}, }; _caseSensitive = config.CaseSensitiveCommands; _defaultRunMode = config.DefaultRunMode; @@ -196,17 +197,19 @@ namespace Discord.Commands //Type Readers public void AddTypeReader(TypeReader reader) { - _typeReaders[typeof(T)] = reader; + var readers = _typeReaders.GetOrAdd(typeof(T), x => new ConcurrentBag()); + readers.Add(reader); } public void AddTypeReader(Type type, TypeReader reader) { - _typeReaders[type] = reader; + var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentBag()); + readers.Add(reader); } - internal TypeReader GetTypeReader(Type type) + internal IEnumerable GetTypeReaders(Type type) { - TypeReader reader; - if (_typeReaders.TryGetValue(type, out reader)) - return reader; + ConcurrentBag definedTypeReaders; + if (_typeReaders.TryGetValue(type, out definedTypeReaders)) + return definedTypeReaders; return null; } From 704b2b75f462641f0d69076acb9f0a884349ba13 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Fri, 25 Nov 2016 20:22:06 +0000 Subject: [PATCH 6/6] Fix changes after review --- .../Builders/ModuleClassBuilder.cs | 19 ++-- .../Builders/ParameterBuilder.cs | 2 +- src/Discord.Net.Commands/CommandService.cs | 90 ++++++++++--------- src/Discord.Net.Commands/Info/CommandInfo.cs | 23 ++--- 4 files changed, 64 insertions(+), 70 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index aaa96fb8..e8dc60de 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -183,9 +183,7 @@ namespace Discord.Commands if (attribute is SummaryAttribute) builder.Summary = (attribute as SummaryAttribute).Text; else if (attribute is OverrideTypeReaderAttribute) - { builder.TypeReader = GetTypeReader(service, paramType, (attribute as OverrideTypeReaderAttribute).TypeReader); - } else if (attribute is ParameterPreconditionAttribute) builder.AddPrecondition(attribute as ParameterPreconditionAttribute); else if (attribute is ParamArrayAttribute) @@ -204,8 +202,7 @@ namespace Discord.Commands if (builder.TypeReader == null) { - var readers = service.GetTypeReaders(paramType); - var reader = readers?.FirstOrDefault(); + var reader = service.GetDefaultTypeReader(paramType); if (reader == null) { @@ -229,20 +226,16 @@ namespace Discord.Commands private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType) { var readers = service.GetTypeReaders(paramType); + TypeReader reader = null; if (readers != null) - { - var reader = readers.FirstOrDefault(x => x.GetType() == typeReaderType); - if (reader != default(TypeReader)) - { + if (readers.TryGetValue(typeReaderType, out reader)) return reader; - } - } //could not find any registered type reader: try to create one - var typeReader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, DependencyMap.Empty); - service.AddTypeReader(paramType, typeReader); + reader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, DependencyMap.Empty); + service.AddTypeReader(paramType, reader); - return typeReader; + return reader; } private static bool IsValidModuleDefinition(TypeInfo typeInfo) diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index 89f89b3c..d4cf598e 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -46,7 +46,7 @@ namespace Discord.Commands.Builders if (readers == null) throw new InvalidOperationException($"{type} does not have a TypeReader registered for it"); - TypeReader = readers.FirstOrDefault(); + TypeReader = readers.FirstOrDefault().Value; if (type.GetTypeInfo().IsValueType) DefaultValue = Activator.CreateInstance(type); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 285a3543..3dc1ca5d 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -14,8 +14,9 @@ namespace Discord.Commands public class CommandService { private readonly SemaphoreSlim _moduleLock; - private readonly ConcurrentDictionary _typedModuleDefs; - private readonly ConcurrentDictionary> _typeReaders; + private readonly ConcurrentDictionary _typedModuleDefs; + private readonly ConcurrentDictionary> _typeReaders; + private readonly ConcurrentDictionary _defaultTypeReaders; private readonly ConcurrentBag _moduleDefs; private readonly CommandMap _map; @@ -24,7 +25,7 @@ namespace Discord.Commands public IEnumerable Modules => _moduleDefs.Select(x => x); public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Commands); - public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value, (a, value) => new {a.Key, value}).ToLookup(x => x.Key, x => x.value); + public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new {y.Key, y.Value})).ToLookup(x => x.Key, x => x.Value); public CommandService() : this(new CommandServiceConfig()) { } public CommandService(CommandServiceConfig config) @@ -33,41 +34,43 @@ namespace Discord.Commands _typedModuleDefs = new ConcurrentDictionary(); _moduleDefs = new ConcurrentBag(); _map = new CommandMap(); - _typeReaders = new ConcurrentDictionary> + _typeReaders = new ConcurrentDictionary>(); + + _defaultTypeReaders = new ConcurrentDictionary { - [typeof(bool)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(char)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(string)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(byte)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(sbyte)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(ushort)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(short)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(uint)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(int)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(ulong)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(long)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(float)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(double)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(decimal)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(DateTime)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(DateTimeOffset)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(bool)] = new SimpleTypeReader(), + [typeof(char)] = new SimpleTypeReader(), + [typeof(string)] = new SimpleTypeReader(), + [typeof(byte)] = new SimpleTypeReader(), + [typeof(sbyte)] = new SimpleTypeReader(), + [typeof(ushort)] = new SimpleTypeReader(), + [typeof(short)] = new SimpleTypeReader(), + [typeof(uint)] = new SimpleTypeReader(), + [typeof(int)] = new SimpleTypeReader(), + [typeof(ulong)] = new SimpleTypeReader(), + [typeof(long)] = new SimpleTypeReader(), + [typeof(float)] = new SimpleTypeReader(), + [typeof(double)] = new SimpleTypeReader(), + [typeof(decimal)] = new SimpleTypeReader(), + [typeof(DateTime)] = new SimpleTypeReader(), + [typeof(DateTimeOffset)] = new SimpleTypeReader(), - [typeof(IMessage)] = new ConcurrentBag{new MessageTypeReader()}, - [typeof(IUserMessage)] = new ConcurrentBag{new MessageTypeReader()}, - [typeof(IChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IDMChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IGroupChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IGuildChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IMessageChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IPrivateChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(ITextChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IVoiceChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IMessage)] = new MessageTypeReader(), + [typeof(IUserMessage)] = new MessageTypeReader(), + [typeof(IChannel)] = new ChannelTypeReader(), + [typeof(IDMChannel)] = new ChannelTypeReader(), + [typeof(IGroupChannel)] = new ChannelTypeReader(), + [typeof(IGuildChannel)] = new ChannelTypeReader(), + [typeof(IMessageChannel)] = new ChannelTypeReader(), + [typeof(IPrivateChannel)] = new ChannelTypeReader(), + [typeof(ITextChannel)] = new ChannelTypeReader(), + [typeof(IVoiceChannel)] = new ChannelTypeReader(), - [typeof(IRole)] = new ConcurrentBag{new RoleTypeReader()}, + [typeof(IRole)] = new RoleTypeReader(), - [typeof(IUser)] = new ConcurrentBag{new UserTypeReader()}, - [typeof(IGroupUser)] = new ConcurrentBag{new UserTypeReader()}, - [typeof(IGuildUser)] = new ConcurrentBag{new UserTypeReader()}, + [typeof(IUser)] = new UserTypeReader(), + [typeof(IGroupUser)] = new UserTypeReader(), + [typeof(IGuildUser)] = new UserTypeReader(), }; _caseSensitive = config.CaseSensitiveCommands; _defaultRunMode = config.DefaultRunMode; @@ -197,21 +200,28 @@ namespace Discord.Commands //Type Readers public void AddTypeReader(TypeReader reader) { - var readers = _typeReaders.GetOrAdd(typeof(T), x => new ConcurrentBag()); - readers.Add(reader); + var readers = _typeReaders.GetOrAdd(typeof(T), x => new ConcurrentDictionary()); + readers[reader.GetType()] = reader; } public void AddTypeReader(Type type, TypeReader reader) { - var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentBag()); - readers.Add(reader); + var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentDictionary()); + readers[reader.GetType()] = reader; } - internal IEnumerable GetTypeReaders(Type type) + internal IDictionary GetTypeReaders(Type type) { - ConcurrentBag definedTypeReaders; + ConcurrentDictionary definedTypeReaders; if (_typeReaders.TryGetValue(type, out definedTypeReaders)) return definedTypeReaders; return null; } + internal TypeReader GetDefaultTypeReader(Type type) + { + TypeReader reader; + if (_defaultTypeReaders.TryGetValue(type, out reader)) + return reader; + return null; + } //Execution public SearchResult Search(CommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos)); diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index a91a1f9f..a6ac5000 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -135,26 +135,17 @@ namespace Discord.Commands if (map == null) map = DependencyMap.Empty; - object[] args = null; - try { - args = GenerateArgs(argList, paramList); - } - catch (Exception ex) - { - return ExecuteResult.FromError(ex); - } + object[] args = GenerateArgs(argList, paramList); - foreach (var parameter in Parameters) - { - var result = await parameter.CheckPreconditionsAsync(context, args, map).ConfigureAwait(false); - if (!result.IsSuccess) - return ExecuteResult.FromError(result); - } + foreach (var parameter in Parameters) + { + var result = await parameter.CheckPreconditionsAsync(context, args, map).ConfigureAwait(false); + if (!result.IsSuccess) + return ExecuteResult.FromError(result); + } - try - { switch (RunMode) { case RunMode.Sync: //Always sync