@@ -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 ImmutableList<(Type EntityType, Type TypeReaderType)> _entityTypeReaders;
private readonly HashSet<ModuleInfo> _moduleDefs;
@@ -77,6 +78,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}"/>; the key is the object type to be read by the <see cref="TypeReader"/>,
/// while 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>
@@ -109,6 +119,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>>();
_defaultTypeReaders = new ConcurrentDictionary<Type, TypeReader>();
foreach (var type in PrimitiveParsers.SupportedTypes)
@@ -329,8 +340,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>
@@ -341,17 +350,49 @@ 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>
public void AddTypeReader(Type type, TypeReader reader)
{
if (_defaultTypeReaders.ContainsKey(type))
_ = _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." +
"To suppress this message, use AddTypeReader<T>(reader, true).");
AddTypeReader(type, reader, true);
var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary<Type, TypeReader>());
readers[reader.GetType()] = reader;
if (type.GetTypeInfo().IsValueType)
AddNullableTypeReader(type, reader);
}
/// <summary>
/// Adds a custom entity <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied
/// object type.
/// </summary>
/// <example>
/// <para>The following example adds a custom entity reader to this <see cref="CommandService"/>.</para>
/// <code language="cs" region="AddEntityTypeReader"
/// source="..\Discord.Net.Examples\Commands\CommandService.Examples.cs" />
/// </example>
/// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam>
/// <param name="typeReaderGenericType">A generic type definition (with one open argument) of the <see cref="TypeReader" />.</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="typeReaderGenericType">A generic type definition (with one open argument) of the <see cref="TypeReader" />.</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
@@ -359,14 +400,20 @@ namespace Discord.Commands
/// If <typeparamref name="T" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> will
/// also be added.
/// </summary>
/// <example>
/// <para>The following example adds a custom entity reader to this <see cref="CommandService"/>.</para>
/// <code language="cs" region="AddEntityTypeReader2"
/// source="..\Discord.Net.Examples\Commands\CommandService.Examples.cs" />
/// </example>
/// <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="replaceDefault">
/// Defines whether the <see cref="TypeReader"/> should replace the default one for
/// <see cref="Type" /> if it exists.
/// </param>
[Obsolete("This method is deprecated. Use the method without the replaceDefault argument.")]
public void AddTypeReader<T>(TypeReader reader, bool replaceDefault)
=> AddTypeReader(typeof(T), reader, replaceDefault );
=> AddTypeReader(typeof(T), reader);
/// <summary>
/// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
/// type.
@@ -379,27 +426,10 @@ namespace Discord.Commands
/// Defines whether the <see cref="TypeReader"/> should replace the default one for <see cref="Type" /> if
/// it exists.
/// </param>
[Obsolete("This method is deprecated. Use the method without the replaceDefault argument.")]
public void AddTypeReader(Type type, TypeReader reader, bool replaceDefault)
{
if (replaceDefault && HasDefaultTypeReader(type))
{
_defaultTypeReaders.AddOrUpdate(type, reader, (k, v) => reader);
if (type.GetTypeInfo().IsValueType)
{
var nullableType = typeof(Nullable<>).MakeGenericType(type);
var nullableReader = NullableTypeReader.Create(type, reader);
_defaultTypeReaders.AddOrUpdate(nullableType, nullableReader, (k, v) => nullableReader);
}
}
else
{
var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary<Type, TypeReader>());
readers[reader.GetType()] = reader;
=> AddTypeReader(type, reader);
if (type.GetTypeInfo().IsValueType)
AddNullableTypeReader(type, reader);
}
}
internal bool HasDefaultTypeReader(Type type)
{
if (_defaultTypeReaders.ContainsKey(type))
@@ -416,10 +446,39 @@ namespace Discord.Commands
var nullableReader = NullableTypeReader.Create(valueType, valueTypeReader);
readers[nullableReader.GetType()] = nullableReader;
}
internal IDictionary<Type, TypeReader> GetTypeReaders(Type typ e)
internal IEnumerable<KeyValuePair<Type, TypeReader>> GetTypeReaders(Type type, bool includeOverrid e)
{
if (_typeReaders.TryGetValue(type, out var definedTypeReaders))
return definedTypeReaders;
return includeOverride ? definedTypeReaders : definedTypeReaders.Where(x => !x.Value.IsOverride);
var assignableEntityReaders = _userEntityTypeReaders.Where(x => x.Key.IsAssignableFrom(type));
int assignableTo = -1;
KeyValuePair<Type, ConcurrentQueue<Type>>? entityReaders = null;
foreach (var entityReader in assignableEntityReaders)
{
int assignables = assignableEntityReaders.Sum(x => !x.Equals(entityReader) && x.Key.IsAssignableFrom(entityReader.Key) ? 1 : 0);
if (assignableTo == -1)
{
// First time
assignableTo = assignables;
entityReaders = entityReader;
}
// 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;
entityReaders = entityReader;
}
}
if (entityReaders != null)
{
var entityTypeReaderType = entityReaders.Value.Value.First();
TypeReader reader = Activator.CreateInstance(entityTypeReaderType.MakeGenericType(type)) as TypeReader;
AddTypeReader(type, reader);
return GetTypeReaders(type, false);
}
return null;
}
internal TypeReader GetDefaultTypeReader(Type type)