@@ -14,8 +14,8 @@ namespace Discord.Commands | |||||
{ | { | ||||
private readonly SemaphoreSlim _moduleLock; | private readonly SemaphoreSlim _moduleLock; | ||||
private readonly ConcurrentDictionary<object, Module> _modules; | private readonly ConcurrentDictionary<object, Module> _modules; | ||||
private readonly ConcurrentDictionary<string, List<Command>> _map; | |||||
private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders; | private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders; | ||||
private readonly CommandMap _map; | |||||
public IEnumerable<Module> Modules => _modules.Select(x => x.Value); | public IEnumerable<Module> Modules => _modules.Select(x => x.Value); | ||||
public IEnumerable<Command> Commands => _modules.SelectMany(x => x.Value.Commands); | public IEnumerable<Command> Commands => _modules.SelectMany(x => x.Value.Commands); | ||||
@@ -24,7 +24,7 @@ namespace Discord.Commands | |||||
{ | { | ||||
_moduleLock = new SemaphoreSlim(1, 1); | _moduleLock = new SemaphoreSlim(1, 1); | ||||
_modules = new ConcurrentDictionary<object, Module>(); | _modules = new ConcurrentDictionary<object, Module>(); | ||||
_map = new ConcurrentDictionary<string, List<Command>>(); | |||||
_map = new CommandMap(); | |||||
_typeReaders = new ConcurrentDictionary<Type, TypeReader> | _typeReaders = new ConcurrentDictionary<Type, TypeReader> | ||||
{ | { | ||||
[typeof(string)] = new GenericTypeReader((m, s) => Task.FromResult(TypeReaderResult.FromSuccess(s))), | [typeof(string)] = new GenericTypeReader((m, s) => Task.FromResult(TypeReaderResult.FromSuccess(s))), | ||||
@@ -160,11 +160,7 @@ namespace Discord.Commands | |||||
_modules[moduleInstance] = loadedModule; | _modules[moduleInstance] = loadedModule; | ||||
foreach (var cmd in loadedModule.Commands) | 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; | return loadedModule; | ||||
} | } | ||||
@@ -222,14 +218,7 @@ namespace Discord.Commands | |||||
if (_modules.TryRemove(module, out unloadedModule)) | if (_modules.TryRemove(module, out unloadedModule)) | ||||
{ | { | ||||
foreach (var cmd in unloadedModule.Commands) | 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; | return true; | ||||
} | } | ||||
else | else | ||||
@@ -240,35 +229,10 @@ namespace Discord.Commands | |||||
public SearchResult Search(IMessage message, string input) | public SearchResult Search(IMessage message, string input) | ||||
{ | { | ||||
string lowerInput = input.ToLowerInvariant(); | 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 | else | ||||
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | 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; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |