diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index b22337559..ff392179e 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -48,6 +48,7 @@ namespace Discord.Commands private readonly SemaphoreSlim _moduleLock; private readonly ConcurrentDictionary _typedModuleDefs; private readonly ConcurrentDictionary> _typeReaders; + private readonly ConcurrentDictionary> _userEntityTypeReaders; private readonly ConcurrentDictionary _defaultTypeReaders; private readonly ConcurrentDictionary _overrideTypeReaders; private readonly ImmutableList<(Type EntityType, Type TypeReaderType)> _entityTypeReaders; @@ -78,6 +79,15 @@ namespace Discord.Commands /// public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value); + /// + /// Represents all entity type reader s loaded within . + /// + /// + /// A that the key is the object type to be read by the + /// and the element is the type of the generic definition. + /// + public ILookup EntityTypeReaders => _userEntityTypeReaders.SelectMany(x => x.Value.Select(y => new { x.Key, TypeReaderType = y })).ToLookup(x => x.Key, y => y.TypeReaderType); + /// /// Initializes a new class. /// @@ -110,6 +120,7 @@ namespace Discord.Commands _moduleDefs = new HashSet(); _map = new CommandMap(this); _typeReaders = new ConcurrentDictionary>(); + _userEntityTypeReaders = new ConcurrentDictionary>(); _overrideTypeReaders = new ConcurrentDictionary(); _defaultTypeReaders = new ConcurrentDictionary(); @@ -331,8 +342,6 @@ namespace Discord.Commands /// type. /// If is a , a nullable will /// also be added. - /// If a default exists for , a warning will be logged - /// and the default will be replaced. /// /// The object type to be read by the . /// An instance of the to be added. @@ -343,8 +352,6 @@ namespace Discord.Commands /// type. /// If is a , a nullable for the /// value type will also be added. - /// If a default exists for , a warning will be logged and - /// the default will be replaced. /// /// A instance for the type to be read. /// An instance of the to be added. @@ -357,6 +364,40 @@ namespace Discord.Commands AddNullableTypeReader(type, reader); } /// + /// Adds a custom entity to this for the supplied + /// object type. + /// + /// The object type to be read by the . + /// + /// A that is a generic type definition with a single open argument + /// of the to be added. + /// + public void AddEntityTypeReader(Type typeReaderGenericType) + => AddEntityTypeReader(typeof(T), typeReaderGenericType); + /// + /// Adds a custom entity to this for the supplied + /// object type. + /// + /// A instance for the type to be read. + /// + /// A that is a generic type definition with a single open argument + /// of the to be added. + /// + 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()); + readers.Enqueue(typeReaderGenericType); + } + /// /// Adds a custom to this for the supplied object /// type. /// If is a , a nullable 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>? typeReader = null; - foreach (var entityReader in entityReaders) + KeyValuePair>? 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) {