| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||