Browse Source

Experimental change for feedback

pull/934/head
Joe4evr 7 years ago
parent
commit
74b17b0e04
6 changed files with 132 additions and 107 deletions
  1. +6
    -6
      docs/guides/getting_started/samples/intro/structure.cs
  2. +17
    -1
      src/Discord.Net.Commands/Builders/ModuleBuilder.cs
  3. +1
    -1
      src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
  4. +101
    -96
      src/Discord.Net.Commands/CommandService.cs
  5. +5
    -1
      src/Discord.Net.Commands/CommandServiceConfig.cs
  6. +2
    -2
      src/Discord.Net.Commands/Info/ModuleInfo.cs

+ 6
- 6
docs/guides/getting_started/samples/intro/structure.cs View File

@@ -19,9 +19,8 @@ class Program

private readonly DiscordSocketClient _client;
// Keep the CommandService and IServiceProvider around for use with commands.
// These two types require you install the Discord.Net.Commands package.
private readonly IServiceProvider _services;
// Keep the CommandService around for use with commands.
// This type requires you install the Discord.Net.Commands package.
private readonly CommandService _commands;

private Program()
@@ -47,6 +46,9 @@ class Program
// Again, log level:
LogLevel = LogSeverity.Info,
// Setup your DI container.
ServiceProvider = ConfigureServices(),
// There's a few more properties you can set,
// for example, case-insensitive commands.
CaseSensitiveCommands = false,
@@ -56,8 +58,6 @@ class Program
_client.Log += Logger;
_commands.Log += Logger;
// Setup your DI container.
_services = ConfigureServices();
}
// If any services require the client, or the CommandService, or something else you keep on hand,
@@ -161,7 +161,7 @@ class Program
// Execute the command. (result does not indicate a return value,
// rather an object stating if the command executed successfully).
var result = await _commands.ExecuteAsync(context, pos, _services);
var result = await _commands.ExecuteAsync(context, pos);

// Uncomment the following lines if you want the bot
// to send a message if it failed (not advised for most situations).


+ 17
- 1
src/Discord.Net.Commands/Builders/ModuleBuilder.cs View File

@@ -26,7 +26,7 @@ namespace Discord.Commands.Builders
public IReadOnlyList<Attribute> Attributes => _attributes;
public IReadOnlyList<string> Aliases => _aliases;

internal Optional<TypeInfo> TypeInfo { get; set; }
internal TypeInfo TypeInfo { get; set; }

//Automatic
internal ModuleBuilder(CommandService service, ModuleBuilder parent)
@@ -120,6 +120,22 @@ namespace Discord.Commands.Builders
if (Name == null)
Name = _aliases[0];

if (TypeInfo != null)
{
//keep this for safety?
//services = services ?? EmptyServiceProvider.Instance;
try
{
var moduleInstance = ReflectionUtils.CreateObject<IModuleBase>(TypeInfo, service, service._serviceProvider);
moduleInstance.OnModuleBuilding(service);
}
catch (Exception)
{
//unsure of what to do here
throw;
}
}

return new ModuleInfo(this, service, parent);
}



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

@@ -297,7 +297,7 @@ namespace Discord.Commands
}

//We dont have a cached type reader, create one
reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, EmptyServiceProvider.Instance);
reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, service._serviceProvider);
service.AddTypeReader(paramType, reader);

return reader;


+ 101
- 96
src/Discord.Net.Commands/CommandService.cs View File

@@ -28,6 +28,8 @@ namespace Discord.Commands
private readonly HashSet<ModuleInfo> _moduleDefs;
private readonly CommandMap _map;

internal readonly IServiceProvider _serviceProvider;

internal readonly bool _caseSensitive, _throwOnError, _ignoreExtraArgs;
internal readonly char _separatorChar;
internal readonly RunMode _defaultRunMode;
@@ -41,6 +43,7 @@ namespace Discord.Commands
public CommandService() : this(new CommandServiceConfig()) { }
public CommandService(CommandServiceConfig config)
{
_serviceProvider = config.ServiceProvider ?? EmptyServiceProvider.Instance;
_caseSensitive = config.CaseSensitiveCommands;
_throwOnError = config.ThrowOnError;
_ignoreExtraArgs = config.IgnoreExtraArgs;
@@ -86,18 +89,18 @@ namespace Discord.Commands
var builder = new ModuleBuilder(this, null, primaryAlias);
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
return LoadModuleInternal(module, null);
return LoadModuleInternal(module);
}
finally
{
_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);
try
@@ -114,14 +117,14 @@ namespace Discord.Commands

_typedModuleDefs[module.Key] = module.Value;

return LoadModuleInternal(module.Value, services);
return LoadModuleInternal(module.Value);
}
finally
{
_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);
try
@@ -132,7 +135,7 @@ namespace Discord.Commands
foreach (var info in moduleDefs)
{
_typedModuleDefs[info.Key] = info.Value;
LoadModuleInternal(info.Value, services);
LoadModuleInternal(info.Value);
}

return moduleDefs.Select(x => x.Value).ToImmutableArray();
@@ -142,31 +145,31 @@ namespace Discord.Commands
_moduleLock.Release();
}
}
private ModuleInfo LoadModuleInternal(ModuleInfo module, IServiceProvider services)
private ModuleInfo LoadModuleInternal(ModuleInfo 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)
_map.AddCommand(command);

foreach (var submodule in module.Submodules)
LoadModuleInternal(submodule, services);
LoadModuleInternal(submodule);

return module;
}
@@ -243,7 +246,7 @@ namespace Discord.Commands
var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), x => new ConcurrentDictionary<Type, TypeReader>());
var nullableReader = NullableTypeReader.Create(valueType, valueTypeReader);
readers[nullableReader.GetType()] = nullableReader;
}
}
internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
{
if (_typeReaders.TryGetValue(type, out var definedTypeReaders))
@@ -291,97 +294,99 @@ namespace Discord.Commands
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);
}
}
}

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

@@ -1,4 +1,6 @@
namespace Discord.Commands
using System;

namespace Discord.Commands
{
public class CommandServiceConfig
{
@@ -18,5 +20,7 @@

/// <summary> Determines whether extra parameters should be ignored. </summary>
public bool IgnoreExtraArgs { get; set; } = false;

public IServiceProvider ServiceProvider { get; set; } = EmptyServiceProvider.Instance;
}
}

+ 2
- 2
src/Discord.Net.Commands/Info/ModuleInfo.cs View File

@@ -22,7 +22,7 @@ namespace Discord.Commands
public ModuleInfo Parent { get; }
public bool IsSubmodule => Parent != null;

internal Optional<TypeInfo> TypeInfo { get; }
//public TypeInfo TypeInfo { get; }

internal ModuleInfo(ModuleBuilder builder, CommandService service, ModuleInfo parent = null)
{
@@ -33,7 +33,7 @@ namespace Discord.Commands
Remarks = builder.Remarks;
Parent = parent;

TypeInfo = builder.TypeInfo;
//TypeInfo = builder.TypeInfo;

Aliases = BuildAliases(builder, service).ToImmutableArray();
Commands = builder.Commands.Select(x => x.Build(this, service)).ToImmutableArray();


Loading…
Cancel
Save