Skip to content
This repository was archived by the owner on Feb 28, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 2 commits
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
4 changes: 2 additions & 2 deletions NeosModLoader/NeosModLoader.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ProjectGuid>{D4627C7F-8091-477A-ABDC-F1465D94D8D9}</ProjectGuid>
Expand All @@ -13,7 +13,7 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<TargetFramework>net462</TargetFramework>
<FileAlignment>512</FileAlignment>
<LangVersion>9.0</LangVersion>
<LangVersion>latestMajor</LangVersion>
<Nullable>enable</Nullable>
<Deterministic>true</Deterministic>
<CopyToLibraries Condition="'$(CopyToLibraries)'==''">true</CopyToLibraries>
Expand Down
134 changes: 134 additions & 0 deletions NeosModLoader/Utility/GenericMethodInvoker.cs
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.");

GenericMethod = method;
}

internal GenericMethodInvoker(MethodInfo method, bool ignoreLackOfGenericParameters)
{
//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)
{
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.");

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)
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)
method = GenericMethod.MakeGenericMethod(definition.Types);
else
method = GenericMethod;

concreteMethods.Add(definition, method);
}

method.Invoke(null, parameters);
}
}
}
166 changes: 166 additions & 0 deletions NeosModLoader/Utility/GenericTypeMethodsInvoker.cs
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();

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);
}

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);
}

private static MethodInfo GetGenericMethodOfConcreteTypeDefault(MethodInfo needleMethod, Type concreteType)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few things I'm confused about.

  1. Wouldn't something like this be equivalent? AccessTools.Method(needleMethod.Name, needleMethod.GetParameters()) Or is there something I'm not seeing?
  2. Needle and Hay are some bonkers variable names. I had a tough time grokking all this code.
  3. If this works consistently then why is there a constructor that lets users pass a custom getGenericMethodOfConcreteType implementation? If this doesn't work consistently then why have it at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Wouldn't something like this be equivalent? AccessTools.Method(needleMethod.Name, needleMethod.GetParameters()) Or is there something I'm not seeing?

I have no idea how that handles instances of the generic parameters of the containing type or the method 🤔

2. Needle and Hay are some bonkers variable names. I had a tough time grokking all this code.

Went with the needle and haystack metaphor for searching something, but I guess it's not super clear in this context lol

3. If this works consistently then why is there a constructor that lets users pass a custom getGenericMethodOfConcreteType implementation? If this doesn't work consistently then why have it at all?

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}"))})");

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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
}
}
}
}
46 changes: 46 additions & 0 deletions NeosModLoader/Utility/TypeDefinition.cs
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()));
}
}
}