-
Notifications
You must be signed in to change notification settings - Fork 21
Add invocation helpers for (generic) methods (on generic types) #89
base: master
Are you sure you want to change the base?
Changes from 2 commits
4d0d8b1
b1e5eff
7c73e42
2eac690
e69cef6
8b38b7a
cc45c1d
01e0285
a43c25a
9177bc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Reflection; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace NeosModLoader.Utility | ||
| { | ||
| public sealed class GenericMethodInvoker<TInstance, TReturn> | ||
| { | ||
| private readonly Dictionary<TypeDefinition, MethodInfo> concreteMethods = new(); | ||
|
|
||
| public MethodInfo GenericMethod { get; } | ||
|
|
||
| public GenericMethodInvoker(MethodInfo method) | ||
| { | ||
| if (!method.ContainsGenericParameters) | ||
| throw new InvalidOperationException("Target method must have remaining open type parameters."); | ||
Banane9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| GenericMethod = method; | ||
| } | ||
|
|
||
| internal GenericMethodInvoker(MethodInfo method, bool ignoreLackOfGenericParameters) | ||
Banane9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| //Intentionally left out generic parameter check to simplify the GenericTypeMethodInvoker | ||
| GenericMethod = method; | ||
| } | ||
|
|
||
| public TReturn Invoke(TInstance instance, Type[] types, params object[] parameters) | ||
| { | ||
| return InvokeInternal(instance, types, parameters); | ||
| } | ||
|
|
||
| public TReturn Invoke(TInstance instance, Type type, params object[] parameters) | ||
| { | ||
| return InvokeInternal(instance, type, parameters); | ||
| } | ||
|
|
||
| internal TReturn InvokeInternal(TInstance? instance, TypeDefinition definition, object[]? parameters) | ||
Banane9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| 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); | ||
| } | ||
| } | ||
|
|
||
| public sealed class GenericMethodInvoker<TReturn> | ||
| { | ||
| private readonly Dictionary<TypeDefinition, MethodInfo> concreteMethods = new(); | ||
|
|
||
| public MethodInfo GenericMethod { get; } | ||
|
|
||
| public GenericMethodInvoker(MethodInfo method) | ||
| { | ||
| if (!method.ContainsGenericParameters) | ||
| throw new InvalidOperationException("Target method must have remaining open type parameters."); | ||
zkxs marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| GenericMethod = method; | ||
| } | ||
|
|
||
| public TReturn Invoke(Type[] types, params object[] parameters) | ||
| { | ||
| return InvokeInternal(types, parameters); | ||
| } | ||
|
|
||
| public TReturn Invoke(Type type, params object[] parameters) | ||
| { | ||
| return InvokeInternal(type, parameters); | ||
| } | ||
|
|
||
| private TReturn InvokeInternal(TypeDefinition definition, object[] parameters) | ||
| { | ||
| if (!concreteMethods.TryGetValue(definition, out var method)) | ||
| { | ||
| if (GenericMethod.ContainsGenericParameters) | ||
Banane9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| method = GenericMethod.MakeGenericMethod(definition.Types); | ||
| else | ||
| method = GenericMethod; | ||
|
|
||
| concreteMethods.Add(definition, method); | ||
| } | ||
|
|
||
| return (TReturn)method.Invoke(null, parameters); | ||
| } | ||
| } | ||
|
|
||
| public sealed class GenericMethodInvoker | ||
| { | ||
| private readonly Dictionary<TypeDefinition, MethodInfo> concreteMethods = new(); | ||
|
|
||
| public MethodInfo GenericMethod { get; } | ||
|
|
||
| public GenericMethodInvoker(MethodInfo method) | ||
| { | ||
| if (!method.ContainsGenericParameters) | ||
| throw new InvalidOperationException("Target method must have remaining open type parameters."); | ||
|
|
||
| GenericMethod = method; | ||
| } | ||
|
|
||
| public void Invoke(Type[] types, params object[] parameters) | ||
| { | ||
| InvokeInternal(types, parameters); | ||
| } | ||
|
|
||
| public void Invoke(Type type, params object[] parameters) | ||
| { | ||
| InvokeInternal(type, parameters); | ||
| } | ||
|
|
||
| private void InvokeInternal(TypeDefinition definition, object[] parameters) | ||
| { | ||
| if (!concreteMethods.TryGetValue(definition, out var method)) | ||
| { | ||
| if (GenericMethod.ContainsGenericParameters) | ||
Banane9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| method = GenericMethod.MakeGenericMethod(definition.Types); | ||
| else | ||
| method = GenericMethod; | ||
|
|
||
| concreteMethods.Add(definition, method); | ||
| } | ||
|
|
||
| method.Invoke(null, parameters); | ||
| } | ||
| } | ||
| } | ||
Banane9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| 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 | ||
| { | ||
| public sealed class GenericTypeMethodsInvoker | ||
| { | ||
| private readonly Dictionary<TypeDefinition, ConcreteType> concreteTypes = new(); | ||
EIA485 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| public Type GenericType { get; } | ||
|
|
||
| public Func<MethodInfo, Type, MethodInfo> GetGenericMethodOfConcreteType { get; } | ||
|
|
||
| public GenericTypeMethodsInvoker(Type genericType) | ||
| : this(genericType, GetGenericMethodOfConcreteTypeDefault) | ||
| { } | ||
|
|
||
| public GenericTypeMethodsInvoker(Type genericType, Func<MethodInfo, Type, MethodInfo> getGenericMethodOfConcreteType) | ||
| { | ||
| GenericType = genericType; | ||
| GetGenericMethodOfConcreteType = getGenericMethodOfConcreteType; | ||
| } | ||
|
|
||
| public TReturn Invoke<TReturn>(MethodInfo method, Type[] instanceTypes, object instance, Type[] methodTypes, params object[] parameters) | ||
| { | ||
| return (TReturn)InvokeInternal(method, instanceTypes, instance, methodTypes, parameters); | ||
| } | ||
|
|
||
| public TReturn Invoke<TReturn>(MethodInfo method, Type[] instanceTypes, object instance, Type methodType, params object[] parameters) | ||
| { | ||
| return (TReturn)InvokeInternal(method, instanceTypes, instance, methodType, parameters); | ||
| } | ||
|
|
||
| public object Invoke(MethodInfo method, Type[] instanceTypes, object instance, Type[] methodTypes, params object[] parameters) | ||
| { | ||
| return InvokeInternal(method, instanceTypes, instance, methodTypes, parameters); | ||
| } | ||
|
|
||
| public object Invoke(MethodInfo method, Type instanceType, object instance, Type[] methodTypes, params object[] parameters) | ||
| { | ||
| return InvokeInternal(method, instanceType, instance, methodTypes, parameters); | ||
| } | ||
|
|
||
| public object Invoke(MethodInfo method, Type[] instanceTypes, object instance, Type methodType, params object[] parameters) | ||
| { | ||
| return InvokeInternal(method, instanceTypes, instance, methodType, parameters); | ||
| } | ||
|
|
||
| public object Invoke(MethodInfo method, Type instanceType, object instance, Type methodType, params object[] parameters) | ||
| { | ||
| return InvokeInternal(method, instanceType, instance, methodType, parameters); | ||
| } | ||
|
|
||
| public TReturn Invoke<TReturn>(MethodInfo method, Type instanceType, object instance, Type[] methodTypes, params object[] parameters) | ||
| { | ||
| return (TReturn)InvokeInternal(method, instanceType, instance, methodTypes, parameters); | ||
| } | ||
|
|
||
| public TReturn Invoke<TReturn>(MethodInfo method, Type instanceType, object instance, Type methodType, params object[] parameters) | ||
| { | ||
| return (TReturn)InvokeInternal(method, instanceType, instance, methodType, parameters); | ||
| } | ||
Banane9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| public TReturn Invoke<TReturn>(MethodInfo method, Type instanceType, object instance, params object[] parameters) | ||
| { | ||
| return (TReturn)InvokeInternal(method, instanceType, instance, new TypeDefinition(), parameters); | ||
| } | ||
|
|
||
| public TReturn Invoke<TReturn>(MethodInfo method, Type instanceType, params object[] parameters) | ||
| { | ||
| return (TReturn)InvokeInternal(method, instanceType, null, new TypeDefinition(), parameters); | ||
| } | ||
|
|
||
| public object Invoke(MethodInfo method, Type[] instanceTypes, object instance, params object[] parameters) | ||
| { | ||
| return InvokeInternal(method, instanceTypes, instance, new TypeDefinition(), parameters); | ||
| } | ||
|
|
||
| public object Invoke(MethodInfo method, Type[] instanceTypes, params object[] parameters) | ||
| { | ||
| return InvokeInternal(method, instanceTypes, null, new TypeDefinition(), parameters); | ||
| } | ||
Banane9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| private static MethodInfo GetGenericMethodOfConcreteTypeDefault(MethodInfo needleMethod, Type concreteType) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a few things I'm confused about.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I have no idea how that handles instances of the generic parameters of the containing type or the method 🤔
Went with the needle and haystack metaphor for searching something, but I guess it's not super clear in this context lol
I'm not sure, having the Func is kind of just what I first came up with |
||
| { | ||
| NeosMod.Debug($"Looking for: {needleMethod.ReturnType.Name} {needleMethod.Name}({string.Join(", ", needleMethod.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"))})"); | ||
|
|
||
| return concreteType.GetMethods(AccessTools.all) | ||
| .Single(hayMethod => | ||
| { | ||
| NeosMod.Debug($"Testing: {hayMethod.ReturnType.Name} {hayMethod.Name}({string.Join(", ", hayMethod.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"))})"); | ||
Banane9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (hayMethod.Name != needleMethod.Name) | ||
| return false; | ||
|
|
||
| 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👀 |
||
| if (hayParameters[i].Name != needleParameters[i].Name) | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| }); | ||
| } | ||
|
|
||
| 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<MethodInfo, GenericMethodInvoker<object, object>> MethodInvokers = new(); | ||
| public readonly Type Type; | ||
|
|
||
| public ConcreteType(Type type) | ||
| { | ||
| Type = type; | ||
| } | ||
|
|
||
| public static implicit operator ConcreteType(Type type) => new(type); | ||
|
|
||
| public GenericMethodInvoker<object, object> GetMethodInvoker(MethodInfo genericMethod, Func<MethodInfo, Type, MethodInfo> getMethod) | ||
| { | ||
| if (!MethodInvokers.TryGetValue(genericMethod, out var methodInvoker)) | ||
| { | ||
| methodInvoker = new GenericMethodInvoker<object, object>(getMethod(genericMethod, Type), true); | ||
| MethodInvokers.Add(genericMethod, methodInvoker); | ||
| } | ||
|
|
||
| return methodInvoker; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| using System; | ||
| using System.Linq; | ||
|
|
||
| namespace NeosModLoader.Utility | ||
| { | ||
| internal readonly struct TypeDefinition : IEquatable<TypeDefinition> | ||
| { | ||
| public readonly Type[] Types; | ||
|
|
||
| public int Length => Types.Length; | ||
|
|
||
| public TypeDefinition(params Type[] types) | ||
| { | ||
| Types = types ?? Array.Empty<Type>(); | ||
| } | ||
|
|
||
| public static implicit operator TypeDefinition(Type[] types) => new(types); | ||
|
|
||
| public static implicit operator TypeDefinition(Type type) | ||
| { | ||
| if (type == null) | ||
| return new(Array.Empty<Type>()); | ||
|
|
||
| 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 override int GetHashCode() | ||
| { | ||
| return unchecked(Types.Aggregate(0, (acc, type) => (-136316459 * acc) + type.GetHashCode())); | ||
Banane9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.