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