diff --git a/NeosModLoader/NeosModLoader.csproj b/NeosModLoader/NeosModLoader.csproj index c4ddc6e..a2ff56c 100644 --- a/NeosModLoader/NeosModLoader.csproj +++ b/NeosModLoader/NeosModLoader.csproj @@ -13,7 +13,7 @@ false net462 512 - 9.0 + 10.0 enable true true diff --git a/NeosModLoader/Utility/GenericMethodInvoker.cs b/NeosModLoader/Utility/GenericMethodInvoker.cs new file mode 100644 index 0000000..9266ac9 --- /dev/null +++ b/NeosModLoader/Utility/GenericMethodInvoker.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace NeosModLoader.Utility +{ + /// + /// Represents the base class for more specific generic method invokers. + /// + /// The type of the instances that the generic method gets invoked on. + /// The type of the generic method's return value. Use object if it depends on the generic type parameters of the method. + public abstract class BaseGenericMethodInvoker + { + private readonly Dictionary concreteMethods = new(); + + /// + /// Gets the generic who's concrete versions will be in invoked by this generic method invoker. + /// + public MethodInfo GenericMethod { get; } + + /// + /// Creates a new generic method invoker for the given method which may not have generic type parameters. + /// + /// Use object for if it depends on the generic type parameters of the method. + /// + /// The generic method to invoke concrete version of. + /// Ignores the method lacking generic type parameters if true. + internal BaseGenericMethodInvoker(MethodInfo method, bool ignoreLackOfGenericParameters) + { + if (!ignoreLackOfGenericParameters && !method.ContainsGenericParameters) + throw new ArgumentException("Target method must have remaining open type parameters.", nameof(method)); + + GenericMethod = method; + } + + /// + /// Creates a new generic method invoker for the given open generic method. + /// + /// Use object for if it depends on the generic type parameters of the method. + /// + /// The generic method to invoke concrete version of. + internal BaseGenericMethodInvoker(MethodInfo method) : this(method, false) + { } + + /// + /// Invokes a concrete version of this invoker's GenericMethod using the given , + /// type parameter and method . + /// + /// The object instance to invoke the method on. Use null for static methods. + /// The generic type parameter definition to create the concrete method with.
+ /// May be ignored if doesn't contain generic parameters. + /// The parameters to invoke the method with. May be empty or null if there is none. + /// The result of the method invocation. + internal TReturn InvokeInternal(TInstance? instance, TypeDefinition definition, params object[]? parameters) + { + if (!concreteMethods.TryGetValue(definition, out var method)) + { + if (GenericMethod.ContainsGenericParameters) + method = GenericMethod.MakeGenericMethod(definition.types); + else + method = GenericMethod; + + concreteMethods.Add(definition, method); + } + + return (TReturn)method.Invoke(instance, parameters); + } + } + + /// + /// Represents a generic method invoker that invokes instance methods with a return value. + /// + /// + public sealed class GenericInstanceMethodInvoker : BaseGenericMethodInvoker + { + /// + /// Creates a new generic method invoker for the given open generic instance method with a return value. + /// + /// Use object for if it depends on the generic type parameters of the method. + /// + /// The generic method to invoke concrete version of. + public GenericInstanceMethodInvoker(MethodInfo method) : base(method) + { } + + internal GenericInstanceMethodInvoker(MethodInfo method, bool ignoreLackOfGenericParameters) + : base(method, ignoreLackOfGenericParameters) + { } + + /// + /// Invokes a concrete version of this invoker's + /// GenericMethod + /// using the given , type parameter and method . + /// + /// The object instance to invoke the method on. + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. + /// The result of the method invocation. + public TReturn Invoke(TInstance instance, TypeDefinition definition, params object[] parameters) + { + return InvokeInternal(instance, definition, parameters); + } + } + + /// + /// Represents a generic method invoker that invokes static methods with a return value. + /// + /// + public sealed class GenericStaticMethodInvoker : BaseGenericMethodInvoker + { + /// + /// Creates a new generic method invoker for the given open generic static method with a return value. + /// + /// Use object for if it depends on the generic type parameters of the method. + /// + /// The generic method to invoke concrete version of. + public GenericStaticMethodInvoker(MethodInfo method) : base(method) + { } + + /// + /// Invokes a concrete version of this invoker's static + /// GenericMethod + /// using the given type parameter and method . + /// + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. + /// The result of the method invocation. + public TReturn Invoke(TypeDefinition definition, params object[] parameters) + { + return InvokeInternal(null, definition, parameters); + } + } + + /// + /// Represents a generic method invoker that invokes static void methods. + /// + public sealed class GenericStaticVoidMethodInvoker : BaseGenericMethodInvoker + { + /// + /// Creates a new generic method invoker for the given open generic static void method. + /// + /// The generic method to invoke concrete version of. + public GenericStaticVoidMethodInvoker(MethodInfo method) : base(method) + { } + + /// + /// Invokes a concrete version of this invoker's static void + /// GenericMethod + /// using the given type parameter and method . + /// + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. + /// The result of the method invocation. + public void Invoke(TypeDefinition definition, params object[] parameters) + { + InvokeInternal(null, definition, parameters); + } + } + + /// + /// Represents a generic method invoker that invokes instance void methods. + /// + /// + public sealed class GenericVoidMethodInvoker : BaseGenericMethodInvoker + { + /// + /// Creates a new generic method invoker for the given open generic instance void method. + /// + /// The generic method to invoke concrete version of. + public GenericVoidMethodInvoker(MethodInfo method) : base(method) + { } + + /// + /// Invokes a concrete version of this invoker's void + /// GenericMethod + /// using the given , type parameter and method . + /// + /// The object instance to invoke the method on. + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. + public void Invoke(TInstance instance, TypeDefinition definition, params object[] parameters) + { + InvokeInternal(instance, definition, parameters); + } + } +} diff --git a/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs b/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs new file mode 100644 index 0000000..1069d67 --- /dev/null +++ b/NeosModLoader/Utility/GenericTypeMethodsInvoker.cs @@ -0,0 +1,295 @@ +using HarmonyLib; +using NeosModLoader; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace NeosModLoader.Utility +{ + /// + /// Represents a generic method invoker that invokes potentially generic methods on generic types. + /// + public sealed class GenericTypeMethodsInvoker + { + private readonly Dictionary concreteTypes = new(); + + /// + /// Gets the generic who's concrete versions will have concrete methods invoked by this invoker. + /// + public Type GenericType { get; } + + /// + /// Gets the function that handles resolving passed in s of the GenericType to ones of the concrete one. + /// + public Func GetGenericMethodOfConcreteType { get; } + + /// + /// Creates a new generic type methods invoker with the given generic type and the default concrete method resolving function. + /// + /// The generic type who's concrete versions will have concrete methods invoked by this invoker. + public GenericTypeMethodsInvoker(Type genericType) + : this(genericType, GetGenericMethodOfConcreteTypeDefault) + { } + + /// + /// Creates a new generic type methods invoker with the given generic type and method resolving function. + /// + /// The generic type who's concrete versions will have concrete methods invoked by this invoker. + /// The function that handles resolving passed in s of the GenericType to ones of the concrete one. + public GenericTypeMethodsInvoker(Type genericType, Func getGenericMethodOfConcreteType) + { + GenericType = genericType; + GetGenericMethodOfConcreteType = getGenericMethodOfConcreteType; + } + + /// + /// Invokes a concrete version of the given generic method using the given , + /// type parameters and method on the + /// concrete version of this instance's GenericType created with the type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The generic type parameter definition to create the concrete instance type with. + /// The instance to invoke the constructed concrete method on. + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + public TReturn Invoke(MethodInfo method, TypeDefinition instanceDefinition, object instance, TypeDefinition methodDefinition, params object[] parameters) + { + return (TReturn)InvokeInternal(method, instanceDefinition, instance, methodDefinition, parameters); + } + + /// + /// Invokes a concrete version of the given generic method using the given , + /// type parameters and method on the + /// concrete version of this instance's GenericType created with the type parameters. + /// + /// The generic method of the generic type to invoke. + /// The generic type parameter definition to create the concrete instance type with. + /// The instance to invoke the constructed concrete method on. + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + public object Invoke(MethodInfo method, TypeDefinition instanceDefinition, object instance, TypeDefinition methodDefinition, params object[] parameters) + { + return InvokeInternal(method, instanceDefinition, instance, methodDefinition, parameters); + } + + /// + /// Invokes a concrete version of the given method on the concrete version of this instance's + /// GenericType created with the type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The generic type parameter definition to create the concrete instance type with. + /// The instance to invoke the constructed concrete method on. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + public TReturn Invoke(MethodInfo method, TypeDefinition instanceDefinition, object instance, params object[] parameters) + { + return (TReturn)InvokeInternal(method, instanceDefinition, instance, new TypeDefinition(), parameters); + } + + /// + /// Invokes a concrete version of the given static method of the concrete version of this instance's + /// GenericType created with the type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The generic type parameter definition to create the concrete instance type with. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + public TReturn Invoke(MethodInfo method, TypeDefinition instanceDefinition, params object[] parameters) + { + return (TReturn)InvokeInternal(method, instanceDefinition, null, new TypeDefinition(), parameters); + } + + /// + /// Invokes a concrete version of the given method on the concrete version of this instance's + /// GenericType created with the type parameters. + /// + /// The generic method of the generic type to invoke. + /// The generic type parameter definition to create the concrete instance type with. + /// The instance to invoke the constructed concrete method on. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + public object Invoke(MethodInfo method, TypeDefinition instanceDefinition, object instance, params object[] parameters) + { + return InvokeInternal(method, instanceDefinition, instance, new TypeDefinition(), parameters); + } + + /// + /// Invokes a concrete version of the given static method of the concrete version of this instance's + /// GenericType created with the type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The generic type parameter definition to create the concrete instance type with. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + public object Invoke(MethodInfo method, TypeDefinition instanceDefinition, params object[] parameters) + { + return InvokeInternal(method, instanceDefinition, null, new TypeDefinition(), parameters); + } + + /// + /// Invokes a concrete version of the given method on the concrete version of this instance's + /// GenericType created with the given 's type parameters. + /// + /// The generic method of the generic type to invoke. + /// The instance to invoke the constructed concrete method on. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + /// The is not descended from the GenericType. + public object Invoke(MethodInfo method, object instance, params object[] parameters) + { + return InvokeInternal(method, GetMatchingGenericTypeArguments(instance), instance, new TypeDefinition(), parameters); + } + + /// + /// Invokes a concrete version of the given generic method using the given , + /// type parameters and method on the + /// concrete version of this instance's GenericType created with the given 's type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The instance to invoke the constructed concrete method on. + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + /// The is not descended from the GenericType. + public object Invoke(MethodInfo method, object instance, TypeDefinition methodDefinition, params object[] parameters) + { + return InvokeInternal(method, GetMatchingGenericTypeArguments(instance), instance, methodDefinition, parameters); + } + + /// + /// Invokes a concrete version of the given generic method using the given , + /// type parameters and method on the + /// concrete version of this instance's GenericType created with the given 's type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The instance to invoke the constructed concrete method on. + /// The generic type parameter definition to create the concrete method with. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + /// The is not descended from the GenericType. + public TReturn Invoke(MethodInfo method, object instance, TypeDefinition methodDefinition, params object[] parameters) + { + return (TReturn)InvokeInternal(method, GetMatchingGenericTypeArguments(instance), instance, methodDefinition, parameters); + } + + /// + /// Invokes a concrete version of the given method on the concrete version of this instance's + /// GenericType created with the given 's type parameters. + /// + /// The invoked method's return type. + /// The generic method of the generic type to invoke. + /// The instance to invoke the constructed concrete method on. + /// The parameters to invoke the method with. May be empty if there is none. + /// The invoked method's return value. + /// The is not descended from the GenericType. + public TReturn Invoke(MethodInfo method, object instance, params object[] parameters) + { + return (TReturn)InvokeInternal(method, GetMatchingGenericTypeArguments(instance), instance, new TypeDefinition(), parameters); + } + + private static MethodInfo GetGenericMethodOfConcreteTypeDefault(MethodInfo needleMethod, Type concreteType) + { + Logger.DebugFuncInternal(() => $"Looking for: {needleMethod.FullDescription()}"); + + return concreteType.GetMethods(AccessTools.all) + .Single(hayMethod => + { + if (hayMethod.Name != needleMethod.Name) + return false; + + Logger.DebugFuncInternal(() => $"Testing potential candidate: {hayMethod.FullDescription()}"); + + var needleParameters = needleMethod.GetParameters(); + var hayParameters = hayMethod.GetParameters(); + + if (hayParameters.Length != needleParameters.Length) + return false; + + for (var i = 0; i < needleParameters.Length; ++i) + { + //var needleParameter = needleParameters[i]; + //var hayParameter = hayParameters[i]; + //var checkType = (hayParameter.ParameterType.IsGenericParameter && needleParameter.ParameterType.IsGenericParameter) + // || (!hayParameter.ParameterType.IsGenericParameter && !needleParameter.ParameterType.IsGenericParameter); + + //NeosMod.Msg($"Comparing: {hayParameter.ParameterType} to {needleParameter.ParameterType} => {hayParameter.ParameterType.FullName == needleParameter.ParameterType.FullName}"); + + //if (checkType && hayParameter.ParameterType.FullName != needleParameter.ParameterType.FullName) + // return false; + + // TODO: Do a proper type check? lol + if (hayParameters[i].Name != needleParameters[i].Name) + return false; + } + + return true; + }); + } + + private Type[] GetMatchingGenericTypeArguments(object instance) + { + var type = instance.GetType(); + + do + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == GenericType) + return type.GenericTypeArguments; + + type = type.BaseType; + } + while (type != null); + + throw new ArgumentException( + $"Provided instance [{instance.GetType().FullDescription()}] was not descended from {nameof(GenericType)} [{GenericType.FullDescription()}].", + nameof(instance)); + } + + private object InvokeInternal(MethodInfo method, TypeDefinition instanceTypes, object? instance, TypeDefinition methodTypes, object[] parameters) + { + if (!concreteTypes.TryGetValue(instanceTypes, out var concreteType)) + { + concreteType = GenericType.MakeGenericType(instanceTypes.types); + concreteTypes.Add(instanceTypes, concreteType); + } + + var methodInvoker = concreteType.GetMethodInvoker(method, GetGenericMethodOfConcreteType); + + return methodInvoker.InvokeInternal(instance, methodTypes, parameters); + } + + private readonly struct ConcreteType + { + public readonly Dictionary> MethodInvokers = new(); + public readonly Type Type; + + public ConcreteType(Type type) + { + Type = type; + } + + public static implicit operator ConcreteType(Type type) => new(type); + + public BaseGenericMethodInvoker GetMethodInvoker(MethodInfo genericMethod, Func getMethod) + { + if (!MethodInvokers.TryGetValue(genericMethod, out var methodInvoker)) + { + methodInvoker = new GenericInstanceMethodInvoker(getMethod(genericMethod, Type), true); + MethodInvokers.Add(genericMethod, methodInvoker); + } + + return methodInvoker; + } + } + } +} diff --git a/NeosModLoader/Utility/TypeDefinition.cs b/NeosModLoader/Utility/TypeDefinition.cs new file mode 100644 index 0000000..14a2e2b --- /dev/null +++ b/NeosModLoader/Utility/TypeDefinition.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace NeosModLoader.Utility +{ + public readonly struct TypeDefinition : IEquatable, IEnumerable + { + internal readonly Type[] types; + + public int Length => types.Length; + + public TypeDefinition(params Type[] types) + => this.types = types ?? Array.Empty(); + + public static implicit operator TypeDefinition(Type[] types) => new(types); + + public static implicit operator TypeDefinition(Type type) + { + if (type == null) + return new(Array.Empty()); + + return new(type); + } + + public static bool operator !=(TypeDefinition left, TypeDefinition right) => !(left == right); + + public static bool operator ==(TypeDefinition left, TypeDefinition right) => left.Equals(right); + + public override bool Equals(object obj) + { + return obj is TypeDefinition definition && Equals(definition); + } + + public bool Equals(TypeDefinition other) + { + return types.SequenceEqual(other.types); + } + + public IEnumerator GetEnumerator() + => ((IEnumerable)types).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => types.GetEnumerator(); + + public override int GetHashCode() + { + return unchecked(types.Aggregate(0, (acc, type) => (31 * acc) + type.GetHashCode())); + } + } +}