Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change priority to user type readers and implement user entity readers #1487

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add AddEntityTypeReader
  • Loading branch information
SubZero0 committed Jun 26, 2020
commit bf144d605a3577809472cb88f2c3005c3979e242
72 changes: 60 additions & 12 deletions src/Discord.Net.Commands/CommandService.cs
Original file line number Diff line number Diff line change
@@ -48,6 +48,7 @@ public class CommandService : IDisposable
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 @@ public class CommandService : IDisposable
/// </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 @@ public CommandService(CommandServiceConfig config)
_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 @@ private bool RemoveModuleInternal(ModuleInfo module)
/// 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 @@ public void AddTypeReader<T>(TypeReader reader)
/// 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 @@ public void AddTypeReader(Type type, TypeReader reader)
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 @@ internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
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)
{