| @@ -48,6 +48,7 @@ namespace Discord.Commands | |||
| private readonly SemaphoreSlim _moduleLock; | |||
| private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs; | |||
| private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>> _typeReaders; | |||
| private readonly ConcurrentDictionary<Type, ConcurrentQueue<Type>> _userEntityTypeReaders; | |||
| private readonly ConcurrentDictionary<Type, TypeReader> _defaultTypeReaders; | |||
| private readonly ConcurrentDictionary<Type, TypeReader> _overrideTypeReaders; | |||
| private readonly ImmutableList<(Type EntityType, Type TypeReaderType)> _entityTypeReaders; | |||
| @@ -78,6 +79,15 @@ namespace Discord.Commands | |||
| /// </summary> | |||
| public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value); | |||
| /// <summary> | |||
| /// Represents all entity type reader <see cref="Type" />s loaded within <see cref="CommandService"/>. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A <see cref="ILookup{TKey, TElement}"/> that the key is the object type to be read by the <see cref="TypeReader"/> | |||
| /// and the element is the type of the <see cref="TypeReader"/> generic definition. | |||
| /// </returns> | |||
| public ILookup<Type, Type> EntityTypeReaders => _userEntityTypeReaders.SelectMany(x => x.Value.Select(y => new { x.Key, TypeReaderType = y })).ToLookup(x => x.Key, y => y.TypeReaderType); | |||
| /// <summary> | |||
| /// Initializes a new <see cref="CommandService"/> class. | |||
| /// </summary> | |||
| @@ -110,6 +120,7 @@ namespace Discord.Commands | |||
| _moduleDefs = new HashSet<ModuleInfo>(); | |||
| _map = new CommandMap(this); | |||
| _typeReaders = new ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>>(); | |||
| _userEntityTypeReaders = new ConcurrentDictionary<Type, ConcurrentQueue<Type>>(); | |||
| _overrideTypeReaders = new ConcurrentDictionary<Type, TypeReader>(); | |||
| _defaultTypeReaders = new ConcurrentDictionary<Type, TypeReader>(); | |||
| @@ -331,8 +342,6 @@ namespace Discord.Commands | |||
| /// type. | |||
| /// If <typeparamref name="T" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> will | |||
| /// also be added. | |||
| /// If a default <see cref="TypeReader" /> exists for <typeparamref name="T" />, a warning will be logged | |||
| /// and the default <see cref="TypeReader" /> will be replaced. | |||
| /// </summary> | |||
| /// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam> | |||
| /// <param name="reader">An instance of the <see cref="TypeReader" /> to be added.</param> | |||
| @@ -343,8 +352,6 @@ namespace Discord.Commands | |||
| /// type. | |||
| /// If <paramref name="type" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> for the | |||
| /// value type will also be added. | |||
| /// If a default <see cref="TypeReader" /> exists for <paramref name="type" />, a warning will be logged and | |||
| /// the default <see cref="TypeReader" /> will be replaced. | |||
| /// </summary> | |||
| /// <param name="type">A <see cref="Type" /> instance for the type to be read.</param> | |||
| /// <param name="reader">An instance of the <see cref="TypeReader" /> to be added.</param> | |||
| @@ -357,6 +364,40 @@ namespace Discord.Commands | |||
| AddNullableTypeReader(type, reader); | |||
| } | |||
| /// <summary> | |||
| /// Adds a custom entity <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied | |||
| /// object type. | |||
| /// </summary> | |||
| /// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam> | |||
| /// <param name="typeReaderType"> | |||
| /// A <see cref="Type" /> that is a generic type definition with a single open argument | |||
| /// of the <see cref="TypeReader" /> to be added. | |||
| /// </param> | |||
| public void AddEntityTypeReader<T>(Type typeReaderGenericType) | |||
| => AddEntityTypeReader(typeof(T), typeReaderGenericType); | |||
| /// <summary> | |||
| /// Adds a custom entity <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied | |||
| /// object type. | |||
| /// </summary> | |||
| /// <param name="type">A <see cref="Type" /> instance for the type to be read.</param> | |||
| /// <param name="typeReaderType"> | |||
| /// A <see cref="Type" /> that is a generic type definition with a single open argument | |||
| /// of the <see cref="TypeReader" /> to be added. | |||
| /// </param> | |||
| public void AddEntityTypeReader(Type type, Type typeReaderGenericType) | |||
| { | |||
| if (!typeReaderGenericType.IsGenericTypeDefinition) | |||
| throw new ArgumentException("TypeReader type must be a generic type definition.", nameof(typeReaderGenericType)); | |||
| Type[] genericArgs = typeReaderGenericType.GetGenericArguments(); | |||
| if (genericArgs.Length != 1) | |||
| throw new ArgumentException("TypeReader type must accept one and only one open generic argument.", nameof(typeReaderGenericType)); | |||
| if (!genericArgs[0].IsGenericParameter) | |||
| throw new ArgumentException("TypeReader type must accept one and only one open generic argument.", nameof(typeReaderGenericType)); | |||
| if (!genericArgs[0].GenericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint)) | |||
| throw new ArgumentException("TypeReader generic argument must have a reference type constraint.", nameof(typeReaderGenericType)); | |||
| var readers = _userEntityTypeReaders.GetOrAdd(type, x => new ConcurrentQueue<Type>()); | |||
| readers.Enqueue(typeReaderGenericType); | |||
| } | |||
| /// <summary> | |||
| /// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object | |||
| /// type. | |||
| /// If <typeparamref name="T" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> will | |||
| @@ -418,28 +459,35 @@ namespace Discord.Commands | |||
| if (_typeReaders.TryGetValue(type, out var definedTypeReaders)) | |||
| return definedTypeReaders; | |||
| var entityReaders = _typeReaders.Where(x => x.Key.IsAssignableFrom(type)); | |||
| var assignableEntityReaders = _userEntityTypeReaders.Where(x => x.Key.IsAssignableFrom(type)); | |||
| int assignableTo = -1; | |||
| KeyValuePair<Type, ConcurrentDictionary<Type, TypeReader>>? typeReader = null; | |||
| foreach (var entityReader in entityReaders) | |||
| KeyValuePair<Type, ConcurrentQueue<Type>>? entityReaders = null; | |||
| foreach (var entityReader in assignableEntityReaders) | |||
| { | |||
| int assignables = entityReaders.Sum(x => !x.Equals(entityReader) && x.Key.IsAssignableFrom(entityReader.Key) ? 1 : 0); | |||
| int assignables = assignableEntityReaders.Sum(x => !x.Equals(entityReader) && x.Key.IsAssignableFrom(entityReader.Key) ? 1 : 0); | |||
| if (assignableTo == -1) | |||
| { | |||
| // First time | |||
| assignableTo = assignables; | |||
| typeReader = entityReader; | |||
| entityReaders = entityReader; | |||
| } | |||
| // Try to get the "higher" interface. IMessageChannel is assignable to IChannel, but not the inverse | |||
| // Try to get the most specific type reader, i.e. IMessageChannel is assignable to IChannel, but not the inverse | |||
| else if (assignables > assignableTo) | |||
| { | |||
| assignableTo = assignables; | |||
| typeReader = entityReader; | |||
| entityReaders = entityReader; | |||
| } | |||
| } | |||
| return typeReader?.Value; | |||
| if (entityReaders != null) | |||
| { | |||
| var entityTypeReaderType = entityReaders.Value.Value.First(); | |||
| TypeReader reader = Activator.CreateInstance(entityTypeReaderType.MakeGenericType(type)) as TypeReader; | |||
| AddTypeReader(type, reader); | |||
| return GetTypeReaders(type); | |||
| } | |||
| return null; | |||
| } | |||
| internal TypeReader GetDefaultTypeReader(Type type) | |||
| { | |||