| @@ -48,6 +48,7 @@ namespace Discord.Commands | |||||
| private readonly SemaphoreSlim _moduleLock; | private readonly SemaphoreSlim _moduleLock; | ||||
| private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs; | private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs; | ||||
| private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>> _typeReaders; | 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> _defaultTypeReaders; | ||||
| private readonly ConcurrentDictionary<Type, TypeReader> _overrideTypeReaders; | private readonly ConcurrentDictionary<Type, TypeReader> _overrideTypeReaders; | ||||
| private readonly ImmutableList<(Type EntityType, Type TypeReaderType)> _entityTypeReaders; | private readonly ImmutableList<(Type EntityType, Type TypeReaderType)> _entityTypeReaders; | ||||
| @@ -78,6 +79,15 @@ namespace Discord.Commands | |||||
| /// </summary> | /// </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); | 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> | /// <summary> | ||||
| /// Initializes a new <see cref="CommandService"/> class. | /// Initializes a new <see cref="CommandService"/> class. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -110,6 +120,7 @@ namespace Discord.Commands | |||||
| _moduleDefs = new HashSet<ModuleInfo>(); | _moduleDefs = new HashSet<ModuleInfo>(); | ||||
| _map = new CommandMap(this); | _map = new CommandMap(this); | ||||
| _typeReaders = new ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>>(); | _typeReaders = new ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>>(); | ||||
| _userEntityTypeReaders = new ConcurrentDictionary<Type, ConcurrentQueue<Type>>(); | |||||
| _overrideTypeReaders = new ConcurrentDictionary<Type, TypeReader>(); | _overrideTypeReaders = new ConcurrentDictionary<Type, TypeReader>(); | ||||
| _defaultTypeReaders = new ConcurrentDictionary<Type, TypeReader>(); | _defaultTypeReaders = new ConcurrentDictionary<Type, TypeReader>(); | ||||
| @@ -331,8 +342,6 @@ namespace Discord.Commands | |||||
| /// type. | /// type. | ||||
| /// If <typeparamref name="T" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> will | /// If <typeparamref name="T" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> will | ||||
| /// also be added. | /// 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> | /// </summary> | ||||
| /// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam> | /// <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> | /// <param name="reader">An instance of the <see cref="TypeReader" /> to be added.</param> | ||||
| @@ -343,8 +352,6 @@ namespace Discord.Commands | |||||
| /// type. | /// type. | ||||
| /// If <paramref name="type" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> for the | /// If <paramref name="type" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> for the | ||||
| /// value type will also be added. | /// 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> | /// </summary> | ||||
| /// <param name="type">A <see cref="Type" /> instance for the type to be read.</param> | /// <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> | /// <param name="reader">An instance of the <see cref="TypeReader" /> to be added.</param> | ||||
| @@ -357,6 +364,40 @@ namespace Discord.Commands | |||||
| AddNullableTypeReader(type, reader); | AddNullableTypeReader(type, reader); | ||||
| } | } | ||||
| /// <summary> | /// <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 | /// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object | ||||
| /// type. | /// type. | ||||
| /// If <typeparamref name="T" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> will | /// 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)) | if (_typeReaders.TryGetValue(type, out var definedTypeReaders)) | ||||
| return definedTypeReaders; | return definedTypeReaders; | ||||
| var entityReaders = _typeReaders.Where(x => x.Key.IsAssignableFrom(type)); | |||||
| var assignableEntityReaders = _userEntityTypeReaders.Where(x => x.Key.IsAssignableFrom(type)); | |||||
| int assignableTo = -1; | 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) | if (assignableTo == -1) | ||||
| { | { | ||||
| // First time | // First time | ||||
| assignableTo = assignables; | 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) | else if (assignables > assignableTo) | ||||
| { | { | ||||
| assignableTo = assignables; | 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) | internal TypeReader GetDefaultTypeReader(Type type) | ||||
| { | { | ||||