|
|
@@ -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) |
|
|
|
{ |
|
|
|