You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

CommandService.cs 13 kB

9 years ago
9 years ago
9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. using Discord.Commands.Builders;
  2. using Discord.Logging;
  3. using System;
  4. using System.Collections.Concurrent;
  5. using System.Collections.Generic;
  6. using System.Collections.Immutable;
  7. using System.Linq;
  8. using System.Reflection;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. namespace Discord.Commands
  12. {
  13. public class CommandService
  14. {
  15. public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } }
  16. internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>();
  17. private readonly SemaphoreSlim _moduleLock;
  18. private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs;
  19. private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>> _typeReaders;
  20. private readonly ConcurrentDictionary<Type, TypeReader> _defaultTypeReaders;
  21. private readonly ImmutableList<Tuple<Type, Type>> _entityTypeReaders; //TODO: Candidate for C#7 Tuple
  22. private readonly HashSet<ModuleInfo> _moduleDefs;
  23. private readonly CommandMap _map;
  24. internal readonly bool _caseSensitive, _throwOnError;
  25. internal readonly char _separatorChar;
  26. internal readonly RunMode _defaultRunMode;
  27. internal readonly Logger _cmdLogger;
  28. internal readonly LogManager _logManager;
  29. public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x);
  30. public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands);
  31. public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new {y.Key, y.Value})).ToLookup(x => x.Key, x => x.Value);
  32. public CommandService() : this(new CommandServiceConfig()) { }
  33. public CommandService(CommandServiceConfig config)
  34. {
  35. _caseSensitive = config.CaseSensitiveCommands;
  36. _throwOnError = config.ThrowOnError;
  37. _separatorChar = config.SeparatorChar;
  38. _defaultRunMode = config.DefaultRunMode;
  39. if (_defaultRunMode == RunMode.Default)
  40. throw new InvalidOperationException("The default run mode cannot be set to Default.");
  41. _logManager = new LogManager(config.LogLevel);
  42. _logManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false);
  43. _cmdLogger = _logManager.CreateLogger("Command");
  44. _moduleLock = new SemaphoreSlim(1, 1);
  45. _typedModuleDefs = new ConcurrentDictionary<Type, ModuleInfo>();
  46. _moduleDefs = new HashSet<ModuleInfo>();
  47. _map = new CommandMap(this);
  48. _typeReaders = new ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>>();
  49. _defaultTypeReaders = new ConcurrentDictionary<Type, TypeReader>();
  50. foreach (var type in PrimitiveParsers.SupportedTypes)
  51. _defaultTypeReaders[type] = PrimitiveTypeReader.Create(type);
  52. var entityTypeReaders = ImmutableList.CreateBuilder<Tuple<Type, Type>>();
  53. entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IMessage), typeof(MessageTypeReader<>)));
  54. entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IChannel), typeof(ChannelTypeReader<>)));
  55. entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IRole), typeof(RoleTypeReader<>)));
  56. entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IUser), typeof(UserTypeReader<>)));
  57. _entityTypeReaders = entityTypeReaders.ToImmutable();
  58. }
  59. //Modules
  60. public async Task<ModuleInfo> CreateModuleAsync(string primaryAlias, Action<ModuleBuilder> buildFunc)
  61. {
  62. await _moduleLock.WaitAsync().ConfigureAwait(false);
  63. try
  64. {
  65. var builder = new ModuleBuilder(this, null, primaryAlias);
  66. buildFunc(builder);
  67. var module = builder.Build(this);
  68. return LoadModuleInternal(module);
  69. }
  70. finally
  71. {
  72. _moduleLock.Release();
  73. }
  74. }
  75. public Task<ModuleInfo> AddModuleAsync<T>() => AddModuleAsync(typeof(T));
  76. public async Task<ModuleInfo> AddModuleAsync(Type type)
  77. {
  78. await _moduleLock.WaitAsync().ConfigureAwait(false);
  79. try
  80. {
  81. var typeInfo = type.GetTypeInfo();
  82. if (_typedModuleDefs.ContainsKey(type))
  83. throw new ArgumentException($"This module has already been added.");
  84. var module = ModuleClassBuilder.Build(this, typeInfo).FirstOrDefault();
  85. if (module.Value == default(ModuleInfo))
  86. throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?");
  87. _typedModuleDefs[module.Key] = module.Value;
  88. return LoadModuleInternal(module.Value);
  89. }
  90. finally
  91. {
  92. _moduleLock.Release();
  93. }
  94. }
  95. public async Task<IEnumerable<ModuleInfo>> AddModulesAsync(Assembly assembly)
  96. {
  97. await _moduleLock.WaitAsync().ConfigureAwait(false);
  98. try
  99. {
  100. var types = ModuleClassBuilder.Search(assembly).ToArray();
  101. var moduleDefs = ModuleClassBuilder.Build(types, this);
  102. foreach (var info in moduleDefs)
  103. {
  104. _typedModuleDefs[info.Key] = info.Value;
  105. LoadModuleInternal(info.Value);
  106. }
  107. return moduleDefs.Select(x => x.Value).ToImmutableArray();
  108. }
  109. finally
  110. {
  111. _moduleLock.Release();
  112. }
  113. }
  114. private ModuleInfo LoadModuleInternal(ModuleInfo module)
  115. {
  116. _moduleDefs.Add(module);
  117. foreach (var command in module.Commands)
  118. _map.AddCommand(command);
  119. foreach (var submodule in module.Submodules)
  120. LoadModuleInternal(submodule);
  121. return module;
  122. }
  123. public async Task<bool> RemoveModuleAsync(ModuleInfo module)
  124. {
  125. await _moduleLock.WaitAsync().ConfigureAwait(false);
  126. try
  127. {
  128. return RemoveModuleInternal(module);
  129. }
  130. finally
  131. {
  132. _moduleLock.Release();
  133. }
  134. }
  135. public Task<bool> RemoveModuleAsync<T>() => RemoveModuleAsync(typeof(T));
  136. public async Task<bool> RemoveModuleAsync(Type type)
  137. {
  138. await _moduleLock.WaitAsync().ConfigureAwait(false);
  139. try
  140. {
  141. ModuleInfo module;
  142. if (!_typedModuleDefs.TryRemove(type, out module))
  143. return false;
  144. return RemoveModuleInternal(module);
  145. }
  146. finally
  147. {
  148. _moduleLock.Release();
  149. }
  150. }
  151. private bool RemoveModuleInternal(ModuleInfo module)
  152. {
  153. if (!_moduleDefs.Remove(module))
  154. return false;
  155. foreach (var cmd in module.Commands)
  156. _map.RemoveCommand(cmd);
  157. foreach (var submodule in module.Submodules)
  158. {
  159. RemoveModuleInternal(submodule);
  160. }
  161. return true;
  162. }
  163. //Type Readers
  164. public void AddTypeReader<T>(TypeReader reader)
  165. {
  166. var readers = _typeReaders.GetOrAdd(typeof(T), x => new ConcurrentDictionary<Type, TypeReader>());
  167. readers[reader.GetType()] = reader;
  168. }
  169. public void AddTypeReader(Type type, TypeReader reader)
  170. {
  171. var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentDictionary<Type, TypeReader>());
  172. readers[reader.GetType()] = reader;
  173. }
  174. internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
  175. {
  176. ConcurrentDictionary<Type, TypeReader> definedTypeReaders;
  177. if (_typeReaders.TryGetValue(type, out definedTypeReaders))
  178. return definedTypeReaders;
  179. return null;
  180. }
  181. internal TypeReader GetDefaultTypeReader(Type type)
  182. {
  183. TypeReader reader;
  184. if (_defaultTypeReaders.TryGetValue(type, out reader))
  185. return reader;
  186. var typeInfo = type.GetTypeInfo();
  187. //Is this an enum?
  188. if (typeInfo.IsEnum)
  189. {
  190. reader = EnumTypeReader.GetReader(type);
  191. _defaultTypeReaders[type] = reader;
  192. return reader;
  193. }
  194. //Is this an entity?
  195. for (int i = 0; i < _entityTypeReaders.Count; i++)
  196. {
  197. if (type == _entityTypeReaders[i].Item1 || typeInfo.ImplementedInterfaces.Contains(_entityTypeReaders[i].Item1))
  198. {
  199. reader = Activator.CreateInstance(_entityTypeReaders[i].Item2.MakeGenericType(type)) as TypeReader;
  200. _defaultTypeReaders[type] = reader;
  201. return reader;
  202. }
  203. }
  204. return null;
  205. }
  206. //Execution
  207. public SearchResult Search(ICommandContext context, int argPos)
  208. => Search(context, context.Message.Content.Substring(argPos));
  209. public SearchResult Search(ICommandContext context, string input)
  210. {
  211. string searchInput = _caseSensitive ? input : input.ToLowerInvariant();
  212. var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Priority).ToImmutableArray();
  213. if (matches.Length > 0)
  214. return SearchResult.FromSuccess(input, matches);
  215. else
  216. return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command.");
  217. }
  218. public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
  219. => ExecuteAsync(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling);
  220. public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
  221. {
  222. dependencyMap = dependencyMap ?? DependencyMap.Empty;
  223. var searchResult = Search(context, input);
  224. if (!searchResult.IsSuccess)
  225. return searchResult;
  226. PreconditionResult? secondOption = null;
  227. var commands = searchResult.Commands;
  228. for (int i = 0; i < commands.Count; i++)
  229. {
  230. var preconditionResult = await commands[i].CheckPreconditionsAsync(context, dependencyMap).ConfigureAwait(false);
  231. if (!preconditionResult.IsSuccess)
  232. {
  233. if (commands.Count == 1)
  234. return preconditionResult;
  235. else if (secondOption != null) //we already got our second option, so we can skip
  236. continue;
  237. }
  238. var parseResult = await commands[i].ParseAsync(context, searchResult).ConfigureAwait(false);
  239. if (!parseResult.IsSuccess)
  240. {
  241. if (parseResult.Error == CommandError.MultipleMatches)
  242. {
  243. IReadOnlyList<TypeReaderValue> argList, paramList;
  244. switch (multiMatchHandling)
  245. {
  246. case MultiMatchHandling.Best:
  247. argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
  248. paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
  249. parseResult = ParseResult.FromSuccess(argList, paramList);
  250. break;
  251. }
  252. }
  253. if (!parseResult.IsSuccess)
  254. {
  255. if (commands.Count == 1)
  256. return parseResult;
  257. else if (secondOption != null) //we already got our second option, so we can skip
  258. continue;
  259. }
  260. }
  261. if (parseResult.IsSuccess && preconditionResult.IsSuccess)
  262. return await commands[i].ExecuteAsync(context, parseResult, dependencyMap).ConfigureAwait(false); // Perfect match and highest priority
  263. else if (secondOption == null && parseResult.IsSuccess)
  264. secondOption = preconditionResult; // It's a parse match, not perfect, but the highest priority
  265. }
  266. if (secondOption != null)
  267. return secondOption.Value;
  268. return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload.");
  269. }
  270. }
  271. }