@@ -14,8 +14,8 @@ namespace Discord.Commands | |||
{ | |||
private readonly SemaphoreSlim _moduleLock; | |||
private readonly ConcurrentDictionary<object, Module> _modules; | |||
private readonly ConcurrentDictionary<string, List<Command>> _map; | |||
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); | |||
@@ -24,7 +24,7 @@ namespace Discord.Commands | |||
{ | |||
_moduleLock = new SemaphoreSlim(1, 1); | |||
_modules = new ConcurrentDictionary<object, Module>(); | |||
_map = new ConcurrentDictionary<string, List<Command>>(); | |||
_map = new CommandMap(); | |||
_typeReaders = new ConcurrentDictionary<Type, TypeReader> | |||
{ | |||
[typeof(string)] = new GenericTypeReader((m, s) => Task.FromResult(TypeReaderResult.FromSuccess(s))), | |||
@@ -160,11 +160,7 @@ namespace Discord.Commands | |||
_modules[moduleInstance] = loadedModule; | |||
foreach (var cmd in loadedModule.Commands) | |||
{ | |||
var list = _map.GetOrAdd(cmd.Text, _ => new List<Command>()); | |||
lock (list) | |||
list.Add(cmd); | |||
} | |||
_map.AddCommand(cmd); | |||
return loadedModule; | |||
} | |||
@@ -222,14 +218,7 @@ namespace Discord.Commands | |||
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); | |||
} | |||
} | |||
_map.RemoveCommand(cmd); | |||
return true; | |||
} | |||
else | |||
@@ -240,35 +229,10 @@ namespace Discord.Commands | |||
public SearchResult Search(IMessage message, string input) | |||
{ | |||
string lowerInput = input.ToLowerInvariant(); | |||
ImmutableArray<Command>.Builder matches = null; | |||
List<Command> group; | |||
int pos = -1; | |||
while (true) | |||
{ | |||
pos = input.IndexOf(' ', pos + 1); | |||
string cmdText = pos == -1 ? input : input.Substring(0, pos); | |||
if (!_map.TryGetValue(cmdText, out group)) | |||
break; | |||
lock (group) | |||
{ | |||
if (matches == null) | |||
matches = ImmutableArray.CreateBuilder<Command>(group.Count); | |||
for (int i = 0; i < group.Count; i++) | |||
matches.Add(group[i]); | |||
} | |||
if (pos == -1) | |||
{ | |||
pos = input.Length; | |||
break; | |||
} | |||
} | |||
var matches = _map.GetCommands(input).ToImmutableArray(); | |||
if (matches != null) | |||
return SearchResult.FromSuccess(input, matches.ToImmutable()); | |||
if (matches.Length > 0) | |||
return SearchResult.FromSuccess(input, matches); | |||
else | |||
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | |||
} | |||
@@ -0,0 +1,76 @@ | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Linq; | |||
namespace Discord.Commands | |||
{ | |||
internal class CommandMap | |||
{ | |||
private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | |||
public CommandMap() | |||
{ | |||
_nodes = new ConcurrentDictionary<string, CommandMapNode>(); | |||
} | |||
public void AddCommand(Command command) | |||
{ | |||
string text = command.Text; | |||
int nextSpace = text.IndexOf(' '); | |||
string name; | |||
lock (this) | |||
{ | |||
if (nextSpace == -1) | |||
name = command.Text; | |||
else | |||
name = command.Text.Substring(0, nextSpace); | |||
var nextNode = _nodes.GetOrAdd(name, x => new CommandMapNode(x)); | |||
nextNode.AddCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command); | |||
} | |||
} | |||
public void RemoveCommand(Command command) | |||
{ | |||
string text = command.Text; | |||
int nextSpace = text.IndexOf(' '); | |||
string name; | |||
lock (this) | |||
{ | |||
if (nextSpace == -1) | |||
name = command.Text; | |||
else | |||
name = command.Text.Substring(0, nextSpace); | |||
CommandMapNode nextNode; | |||
if (_nodes.TryGetValue(name, out nextNode)) | |||
{ | |||
nextNode.AddCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command); | |||
if (nextNode.IsEmpty) | |||
_nodes.TryRemove(name, out nextNode); | |||
} | |||
} | |||
} | |||
public IEnumerable<Command> GetCommands(string text) | |||
{ | |||
int nextSpace = text.IndexOf(' '); | |||
string name; | |||
lock (this) | |||
{ | |||
if (nextSpace == -1) | |||
name = text; | |||
else | |||
name = text.Substring(0, nextSpace); | |||
CommandMapNode nextNode; | |||
if (_nodes.TryGetValue(name, out nextNode)) | |||
return nextNode.GetCommands(text, nextSpace + 1); | |||
else | |||
return Enumerable.Empty<Command>(); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,95 @@ | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
namespace Discord.Commands | |||
{ | |||
internal class CommandMapNode | |||
{ | |||
private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | |||
private readonly string _name; | |||
private ImmutableArray<Command> _commands; | |||
public bool IsEmpty => _commands.Length == 0 && _nodes.Count == 0; | |||
public CommandMapNode(string name) | |||
{ | |||
_name = name; | |||
_nodes = new ConcurrentDictionary<string, CommandMapNode>(); | |||
_commands = ImmutableArray.Create<Command>(); | |||
} | |||
public void AddCommand(string text, int index, Command command) | |||
{ | |||
int nextSpace = text.IndexOf(' ', index); | |||
string name; | |||
lock (this) | |||
{ | |||
if (text == "") | |||
_commands = _commands.Add(command); | |||
else | |||
{ | |||
if (nextSpace == -1) | |||
name = text.Substring(index); | |||
else | |||
name = text.Substring(index, nextSpace - index); | |||
var nextNode = _nodes.GetOrAdd(name, x => new CommandMapNode(x)); | |||
nextNode.AddCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command); | |||
} | |||
} | |||
} | |||
public void RemoveCommand(string text, int index, Command command) | |||
{ | |||
int nextSpace = text.IndexOf(' ', index); | |||
string name; | |||
lock (this) | |||
{ | |||
if (text == "") | |||
_commands = _commands.Remove(command); | |||
else | |||
{ | |||
if (nextSpace == -1) | |||
name = text.Substring(index); | |||
else | |||
name = text.Substring(index, nextSpace - index); | |||
CommandMapNode nextNode; | |||
if (_nodes.TryGetValue(name, out nextNode)) | |||
{ | |||
nextNode.RemoveCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command); | |||
if (nextNode.IsEmpty) | |||
_nodes.TryRemove(name, out nextNode); | |||
} | |||
} | |||
} | |||
} | |||
public IEnumerable<Command> GetCommands(string text, int index) | |||
{ | |||
int nextSpace = text.IndexOf(' ', index); | |||
string name; | |||
var commands = _commands; | |||
for (int i = 0; i < commands.Length; i++) | |||
yield return _commands[i]; | |||
if (text != "") | |||
{ | |||
if (nextSpace == -1) | |||
name = text.Substring(index); | |||
else | |||
name = text.Substring(index, nextSpace - index); | |||
CommandMapNode nextNode; | |||
if (_nodes.TryGetValue(name, out nextNode)) | |||
{ | |||
foreach (var cmd in nextNode.GetCommands(nextSpace == -1 ? "" : text, nextSpace + 1)) | |||
yield return cmd; | |||
} | |||
} | |||
} | |||
} | |||
} |