Browse Source

Reorganized commands structure

tags/1.0-rc
RogueException 9 years ago
parent
commit
32ab967f4a
7 changed files with 164 additions and 127 deletions
  1. +5
    -1
      src/Discord.Net.Commands/Attributes/CommandAttribute.cs
  2. +35
    -0
      src/Discord.Net.Commands/Command.cs
  3. +0
    -71
      src/Discord.Net.Commands/CommandMap.cs
  4. +65
    -53
      src/Discord.Net.Commands/CommandService.cs
  5. +33
    -0
      src/Discord.Net.Commands/Module.cs
  6. +24
    -0
      src/Discord.Net.Commands/ReflectionUtils.cs
  7. +2
    -2
      src/Discord.Net.Commands/SearchResults.cs

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

@@ -5,9 +5,13 @@ namespace Discord.Commands
[AttributeUsage(AttributeTargets.Method)]
public class CommandAttribute : Attribute
{
public string Text { get; }
public string Name { get; }
public CommandAttribute(string name)

public CommandAttribute(string name) : this(name, name) { }
public CommandAttribute(string text, string name)
{
Text = text.ToLowerInvariant();
Name = name;
}
}


+ 35
- 0
src/Discord.Net.Commands/Command.cs View File

@@ -0,0 +1,35 @@
using System;
using System.Reflection;

namespace Discord.Commands
{
public class Command
{
private Action<IMessage> _action;

public string Name { get; }
public string Description { get; }
public string Text { get; }

internal Command(CommandAttribute attribute, MethodInfo methodInfo)
{
var description = methodInfo.GetCustomAttribute<DescriptionAttribute>();
if (description != null)
Description = description.Text;

Name = attribute.Name;
Text = attribute.Text;
}

public void Invoke(IMessage msg)
{
_action.Invoke(msg);
}

private void BuildAction()
{
_action = null;
//TODO: Implement
}
}
}

+ 0
- 71
src/Discord.Net.Commands/CommandMap.cs View File

@@ -1,71 +0,0 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;

namespace Discord.Commands
{
public class CommandMap
{
private readonly ConcurrentDictionary<string, List<Command>> _map;

public CommandMap()
{
_map = new ConcurrentDictionary<string, List<Command>>();
}

public void Add(string key, Command cmd)
{
var list = _map.GetOrAdd(key, _ => new List<Command>());
lock (list)
list.Add(cmd);
}
public void Remove(string key, Command cmd)
{
List<Command> list;
if (_map.TryGetValue(key, out list))
{
lock (list)
list.Remove(cmd);
}
}
public IReadOnlyList<Command> Get(string key)
{
List<Command> list;
if (_map.TryGetValue(key, out list))
{
lock (list)
return list.ToImmutableArray();
}
return ImmutableArray.Create<Command>();
}

//TODO: C#7 Candidate for tuple
public CommandSearchResults Search(string input)
{
string lowerInput = input.ToLowerInvariant();

List<Command> bestGroup = null, group;
int startPos = 0, endPos;

while (true)
{
endPos = input.IndexOf(' ', startPos);
string cmdText = endPos == -1 ? input.Substring(startPos) : input.Substring(startPos, endPos - startPos);
startPos = endPos + 1;
if (!_map.TryGetValue(cmdText, out group))
break;
bestGroup = group;
}

ImmutableArray<Command> cmds;
if (bestGroup != null)
{
lock (bestGroup)
cmds = bestGroup.ToImmutableArray();
}
else
cmds = ImmutableArray.Create<Command>();
return new CommandSearchResults(cmds, startPos);
}
}
}

src/Discord.Net.Commands/CommandParser.cs → src/Discord.Net.Commands/CommandService.cs View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Threading;
@@ -7,55 +9,22 @@ using System.Threading.Tasks;

namespace Discord.Commands
{
public class Module
{
public string Name { get; }
public IEnumerable<Command> Commands { get; }
internal Module(object parent, TypeInfo typeInfo)
{
List<Command> commands = new List<Command>();
SearchClass(parent, commands, typeInfo);
Commands = commands;
}

private void SearchClass(object parent, List<Command> commands, TypeInfo typeInfo)
{
foreach (var method in typeInfo.DeclaredMethods)
{
if (typeInfo.GetCustomAttribute<CommandAttribute>() != null)
{

}
}
foreach (var type in typeInfo.DeclaredNestedTypes)
{
if (typeInfo.GetCustomAttribute<GroupAttribute>() != null)
{
SearchClass(CommandParser.CreateObject(typeInfo), commands, type);
}
}
}
}
public class Command
{
public string SourceName { get; }

internal Command(TypeInfo typeInfo)
{
}
}

public class CommandParser
public class CommandService
{
private readonly SemaphoreSlim _moduleLock;
private readonly Dictionary<object, Module> _modules;
private readonly ConcurrentDictionary<object, Module> _modules;
private readonly ConcurrentDictionary<string, List<Command>> _map;

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

public CommandParser()
public CommandService()
{
_modules = new Dictionary<object, Module>();
_moduleLock = new SemaphoreSlim(1, 1);
_modules = new ConcurrentDictionary<object, Module>();
_map = new ConcurrentDictionary<string, List<Command>>();
}

public async Task<Module> Load(object module)
{
await _moduleLock.WaitAsync().ConfigureAwait(false);
@@ -63,9 +32,11 @@ namespace Discord.Commands
{
if (_modules.ContainsKey(module))
throw new ArgumentException($"This module has already been loaded.");

var typeInfo = module.GetType().GetTypeInfo();
if (typeInfo.GetCustomAttribute<ModuleAttribute>() == null)
throw new ArgumentException($"Modules must be marked with ModuleAttribute.");

return LoadInternal(module, typeInfo);
}
finally
@@ -77,6 +48,14 @@ namespace Discord.Commands
{
var loadedModule = new Module(module, typeInfo);
_modules[module] = loadedModule;

foreach (var cmd in loadedModule.Commands)
{
var list = _map.GetOrAdd(cmd.Text, _ => new List<Command>());
lock (list)
list.Add(cmd);
}

return loadedModule;
}
public async Task<IEnumerable<Module>> LoadAssembly(Assembly assembly)
@@ -90,7 +69,7 @@ namespace Discord.Commands
var typeInfo = type.GetTypeInfo();
if (typeInfo.GetCustomAttribute<ModuleAttribute>() != null)
{
var module = CreateObject(typeInfo);
var module = ReflectionUtils.CreateObject(typeInfo);
modules.Add(LoadInternal(module, typeInfo));
}
}
@@ -107,27 +86,60 @@ namespace Discord.Commands
await _moduleLock.WaitAsync().ConfigureAwait(false);
try
{
return _modules.Remove(module);
return UnloadInternal(module);
}
finally
{
_moduleLock.Release();
}
}
private bool UnloadInternal(object module)
{
Module unloadedModule;
if (_modules.TryRemove(module, out unloadedModule))
{
foreach (var cmd in unloadedModule.Commands)
{
List<Command> list;
if (_map.TryGetValue(cmd.Text, out list))
{
lock (list)
list.Remove(cmd);
}
}
return true;
}
else
return false;
}

internal static object CreateObject(TypeInfo typeInfo)
//TODO: C#7 Candidate for tuple
public SearchResults Search(string input)
{
var constructor = typeInfo.DeclaredConstructors.Where(x => x.GetParameters().Length == 0).FirstOrDefault();
if (constructor == null)
throw new InvalidOperationException($"Failed to find a valid constructor for \"{typeInfo.FullName}\"");
try
string lowerInput = input.ToLowerInvariant();

List<Command> bestGroup = null, group;
int startPos = 0, endPos;

while (true)
{
return constructor.Invoke(null);
endPos = input.IndexOf(' ', startPos);
string cmdText = endPos == -1 ? input.Substring(startPos) : input.Substring(startPos, endPos - startPos);
startPos = endPos + 1;
if (!_map.TryGetValue(cmdText, out group))
break;
bestGroup = group;
}
catch (Exception ex)

ImmutableArray<Command> cmds;
if (bestGroup != null)
{
throw new InvalidOperationException($"Failed to create \"{typeInfo.FullName}\"", ex);
lock (bestGroup)
cmds = bestGroup.ToImmutableArray();
}
else
cmds = ImmutableArray.Create<Command>();
return new SearchResults(cmds, startPos);
}
}
}

+ 33
- 0
src/Discord.Net.Commands/Module.cs View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Reflection;

namespace Discord.Commands
{
public class Module
{
public string Name { get; }
public IEnumerable<Command> Commands { get; }

internal Module(object parent, TypeInfo typeInfo)
{
List<Command> commands = new List<Command>();
SearchClass(parent, commands, typeInfo);
Commands = commands;
}

private void SearchClass(object parent, List<Command> commands, TypeInfo typeInfo)
{
foreach (var method in typeInfo.DeclaredMethods)
{
var cmdAttr = method.GetCustomAttribute<CommandAttribute>();
if (cmdAttr != null)
commands.Add(new Command(cmdAttr, method));
}
foreach (var type in typeInfo.DeclaredNestedTypes)
{
if (type.GetCustomAttribute<GroupAttribute>() != null)
SearchClass(ReflectionUtils.CreateObject(type), commands, type);
}
}
}
}

+ 24
- 0
src/Discord.Net.Commands/ReflectionUtils.cs View File

@@ -0,0 +1,24 @@
using System;
using System.Linq;
using System.Reflection;

namespace Discord.Commands
{
internal class ReflectionUtils
{
internal static object CreateObject(TypeInfo typeInfo)
{
var constructor = typeInfo.DeclaredConstructors.Where(x => x.GetParameters().Length == 0).FirstOrDefault();
if (constructor == null)
throw new InvalidOperationException($"Failed to find a valid constructor for \"{typeInfo.FullName}\"");
try
{
return constructor.Invoke(null);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to create \"{typeInfo.FullName}\"", ex);
}
}
}
}

src/Discord.Net.Commands/CommandSearchResults.cs → src/Discord.Net.Commands/SearchResults.cs View File

@@ -2,12 +2,12 @@

namespace Discord.Commands
{
public struct CommandSearchResults
public struct SearchResults
{
IReadOnlyList<Command> Commands { get; }
int ArgsPos { get; }

public CommandSearchResults(IReadOnlyList<Command> commands, int argsPos)
public SearchResults(IReadOnlyList<Command> commands, int argsPos)
{
Commands = commands;
ArgsPos = argsPos;

Loading…
Cancel
Save