@@ -28,6 +28,8 @@ namespace Discord.Commands
private readonly HashSet<ModuleInfo> _moduleDefs;
private readonly HashSet<ModuleInfo> _moduleDefs;
private readonly CommandMap _map;
private readonly CommandMap _map;
internal readonly IServiceProvider _serviceProvider;
internal readonly bool _caseSensitive, _throwOnError, _ignoreExtraArgs;
internal readonly bool _caseSensitive, _throwOnError, _ignoreExtraArgs;
internal readonly char _separatorChar;
internal readonly char _separatorChar;
internal readonly RunMode _defaultRunMode;
internal readonly RunMode _defaultRunMode;
@@ -41,6 +43,7 @@ namespace Discord.Commands
public CommandService() : this(new CommandServiceConfig()) { }
public CommandService() : this(new CommandServiceConfig()) { }
public CommandService(CommandServiceConfig config)
public CommandService(CommandServiceConfig config)
{
{
_serviceProvider = config.ServiceProvider ?? EmptyServiceProvider.Instance;
_caseSensitive = config.CaseSensitiveCommands;
_caseSensitive = config.CaseSensitiveCommands;
_throwOnError = config.ThrowOnError;
_throwOnError = config.ThrowOnError;
_ignoreExtraArgs = config.IgnoreExtraArgs;
_ignoreExtraArgs = config.IgnoreExtraArgs;
@@ -86,18 +89,18 @@ namespace Discord.Commands
var builder = new ModuleBuilder(this, null, primaryAlias);
var builder = new ModuleBuilder(this, null, primaryAlias);
buildFunc(builder);
buildFunc(builder);
var module = builder.Build(this);
var module = builder.Build(this, null );
//should be fine to pass null here since it'll never get checked from this path anyway
//should be fine to pass null here since it'll never get checked from this path anyway
return LoadModuleInternal(module, null );
return LoadModuleInternal(module);
}
}
finally
finally
{
{
_moduleLock.Release();
_moduleLock.Release();
}
}
}
}
public Task<ModuleInfo> AddModuleAsync<T>(IServiceProvider services ) => AddModuleAsync(typeof(T), services );
public async Task<ModuleInfo> AddModuleAsync(Type type, IServiceProvider services )
public Task<ModuleInfo> AddModuleAsync<T>() => AddModuleAsync(typeof(T));
public async Task<ModuleInfo> AddModuleAsync(Type type)
{
{
await _moduleLock.WaitAsync().ConfigureAwait(false);
await _moduleLock.WaitAsync().ConfigureAwait(false);
try
try
@@ -114,14 +117,14 @@ namespace Discord.Commands
_typedModuleDefs[module.Key] = module.Value;
_typedModuleDefs[module.Key] = module.Value;
return LoadModuleInternal(module.Value, services );
return LoadModuleInternal(module.Value);
}
}
finally
finally
{
{
_moduleLock.Release();
_moduleLock.Release();
}
}
}
}
public async Task<IEnumerable<ModuleInfo>> AddModulesAsync(Assembly assembly, IServiceProvider services )
public async Task<IEnumerable<ModuleInfo>> AddModulesAsync(Assembly assembly)
{
{
await _moduleLock.WaitAsync().ConfigureAwait(false);
await _moduleLock.WaitAsync().ConfigureAwait(false);
try
try
@@ -132,7 +135,7 @@ namespace Discord.Commands
foreach (var info in moduleDefs)
foreach (var info in moduleDefs)
{
{
_typedModuleDefs[info.Key] = info.Value;
_typedModuleDefs[info.Key] = info.Value;
LoadModuleInternal(info.Value, services );
LoadModuleInternal(info.Value);
}
}
return moduleDefs.Select(x => x.Value).ToImmutableArray();
return moduleDefs.Select(x => x.Value).ToImmutableArray();
@@ -142,31 +145,31 @@ namespace Discord.Commands
_moduleLock.Release();
_moduleLock.Release();
}
}
}
}
private ModuleInfo LoadModuleInternal(ModuleInfo module, IServiceProvider services )
private ModuleInfo LoadModuleInternal(ModuleInfo module)
{
{
_moduleDefs.Add(module);
_moduleDefs.Add(module);
if (module.TypeInfo.IsSpecified)
{
//keep this for safety?
services = services ?? EmptyServiceProvider.Instance;
try
{
var moduleInstance = ReflectionUtils.CreateObject<IModuleBase>(module.TypeInfo.Value, this, services);
moduleInstance.OnModuleBuilding(this);
}
catch(Exception)
{
//unsure of what to do here
throw;
}
}
// if (module.TypeInfo.IsSpecified)
// {
// //keep this for safety?
// services = services ?? EmptyServiceProvider.Instance;
// try
// {
// var moduleInstance = ReflectionUtils.CreateObject<IModuleBase>(module.TypeInfo.Value, this, services);
// moduleInstance.OnModuleBuilding(this);
// }
// catch(Exception)
// {
// //unsure of what to do here
// throw;
// }
// }
foreach (var command in module.Commands)
foreach (var command in module.Commands)
_map.AddCommand(command);
_map.AddCommand(command);
foreach (var submodule in module.Submodules)
foreach (var submodule in module.Submodules)
LoadModuleInternal(submodule, services );
LoadModuleInternal(submodule);
return module;
return module;
}
}
@@ -243,7 +246,7 @@ namespace Discord.Commands
var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), x => new ConcurrentDictionary<Type, TypeReader>());
var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), x => new ConcurrentDictionary<Type, TypeReader>());
var nullableReader = NullableTypeReader.Create(valueType, valueTypeReader);
var nullableReader = NullableTypeReader.Create(valueType, valueTypeReader);
readers[nullableReader.GetType()] = nullableReader;
readers[nullableReader.GetType()] = nullableReader;
}
}
internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
{
{
if (_typeReaders.TryGetValue(type, out var definedTypeReaders))
if (_typeReaders.TryGetValue(type, out var definedTypeReaders))
@@ -291,97 +294,99 @@ namespace Discord.Commands
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command.");
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command.");
}
}
public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
=> ExecuteAsync(context, context.Message.Content.Substring(argPos), services, multiMatchHandling);
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, /* IServiceProvider services = null,*/ MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
=> ExecuteAsync(context, context.Message.Content.Substring(argPos), /* services,*/ multiMatchHandling);
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, /* IServiceProvider services = null,*/ MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
{
{
services = services ?? EmptyServiceProvider.Instance;
var searchResult = Search(context, input);
if (!searchResult.IsSuccess)
return searchResult;
var commands = searchResult.Commands;
var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>();
foreach (var match in commands)
//services = services ?? EmptyServiceProvider.Instance;
using (var scope = _serviceProvider.CreateScope())
{
{
preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false);
}
var searchResult = Search(context, input);
if (!searchResult.IsSuccess)
return searchResult;
var successfulPreconditions = preconditionResults
.Where(x => x.Value.IsSuccess)
.ToArray();
var commands = searchResult.Commands;
var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>();
if (successfulPreconditions.Length == 0)
{
//All preconditions failed, return the one from the highest priority command
var bestCandidate = preconditionResults
.OrderByDescending(x => x.Key.Command.Priority)
.FirstOrDefault(x => !x.Value.IsSuccess);
return bestCandidate.Value;
}
foreach (var match in commands)
{
preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, scope.ServiceProvider).ConfigureAwait(false);
}
//If we get this far, at least one precondition was successful.
var successfulPreconditions = preconditionResults
.Where(x => x.Value.IsSuccess)
.ToArray();
var parseResultsDict = new Dictionary<CommandMatch, ParseResult>();
foreach (var pair in successfulPreconditions)
{
var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false);
if (successfulPreconditions.Length == 0)
{
//All preconditions failed, return the one from the highest priority command
var bestCandidate = preconditionResults
.OrderByDescending(x => x.Key.Command.Priority)
.FirstOrDefault(x => !x.Value.IsSuccess);
return bestCandidate.Value;
}
//If we get this far, at least one precondition was successful.
if (parseResult.Error == CommandError.MultipleMatches)
var parseResultsDict = new Dictionary<CommandMatch, ParseResult>();
foreach (var pair in successfulPreconditions)
{
{
IReadOnlyList<TypeReaderValue> argList, paramList;
switch (multiMatchHandling)
var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, scope.ServiceProvider).ConfigureAwait(false);
if (parseResult.Error == CommandError.MultipleMatches)
{
{
case MultiMatchHandling.Best:
argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
parseResult = ParseResult.FromSuccess(argList, paramList);
break;
IReadOnlyList<TypeReaderValue> argList, paramList;
switch (multiMatchHandling)
{
case MultiMatchHandling.Best:
argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
parseResult = ParseResult.FromSuccess(argList, paramList);
break;
}
}
}
}
parseResultsDict[pair.Key] = parseResult;
}
parseResultsDict[pair.Key] = parseResult;
}
// Calculates the 'score' of a command given a parse result
float CalculateScore(CommandMatch match, ParseResult parseResult)
{
float argValuesScore = 0, paramValuesScore = 0;
if (match.Command.Parameters.Count > 0)
// Calculates the 'score' of a command given a parse result
float CalculateScore(CommandMatch match, ParseResult parseResult)
{
{
var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
float argValuesScore = 0, paramValuesScore = 0;
argValuesScore = argValuesSum / match.Command.Parameters.Count;
paramValuesScore = paramValuesSum / match.Command.Parameters.Count;
if (match.Command.Parameters.Count > 0)
{
var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
argValuesScore = argValuesSum / match.Command.Parameters.Count;
paramValuesScore = paramValuesSum / match.Command.Parameters.Count;
}
var totalArgsScore = (argValuesScore + paramValuesScore) / 2;
return match.Command.Priority + totalArgsScore * 0.99f;
}
}
var totalArgsScore = (argValuesScore + paramValuesScore) / 2;
return match.Command.Priority + totalArgsScore * 0.99f;
}
//Order the parse results by their score so that we choose the most likely result to execute
var parseResults = parseResultsDict
.OrderByDescending(x => CalculateScore(x.Key, x.Value));
//Order the parse results by their score so that we choose the most likely result to execute
var parseResults = parseResultsDict
.OrderByDescending(x => CalculateScore(x.Key, x.Value));
var successfulParses = parseResults
.Where(x => x.Value.IsSuccess)
.ToArray( );
var successfulParses = parseResults
.Where(x => x.Value.IsSuccess)
.ToArray();
if (successfulParses.Length == 0)
{
//All parses failed, return the one from the highest priority command, using score as a tie breaker
var bestMatch = parseResults
.FirstOrDefault(x => !x.Value.IsSuccess);
return bestMatch.Value;
}
if (successfulParses.Length == 0)
{
//All parses failed, return the one from the highest priority command, using score as a tie breaker
var bestMatch = parseResults
.FirstOrDefault(x => !x.Value.IsSuccess);
return bestMatch.Value;
//If we get this far, at least one parse was successful. Execute the most likely overload.
var chosenOverload = successfulParses[0];
return await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, scope.ServiceProvider).ConfigureAwait(false);
}
}
//If we get this far, at least one parse was successful. Execute the most likely overload.
var chosenOverload = successfulParses[0];
return await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false);
}
}
}
}
}
}