Skip to content

Commit

Permalink
Add handler cache for MakeDeepCopy
Browse files Browse the repository at this point in the history
  • Loading branch information
Mhburg committed Sep 25, 2020
1 parent 133c213 commit 2f5c171
Showing 1 changed file with 43 additions and 15 deletions.
58 changes: 43 additions & 15 deletions Harmony/Tools/AccessTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,21 @@
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization;
using System.Threading;

namespace HarmonyLib
{
/// <summary>A helper class for reflection related functions</summary>
///
public static class AccessTools
{
/// <summary>
/// A cache for the <see cref="ICollection{T}.Add"/> or similar Add methods for different types.
/// </summary>
private static readonly Dictionary<Type, FastInvokeHandler> handlerCache = new Dictionary<Type, FastInvokeHandler>();

private static readonly ReaderWriterLockSlim handlerCacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

/// <summary>Shortcut for <see cref="BindingFlags"/> to simplify the use of reflections and make it work for any access level</summary>
///
// Note: This should a be const, but changing from static (readonly) to const breaks binary compatibility.
Expand Down Expand Up @@ -1687,25 +1695,45 @@ public static object MakeDeepCopy(object source, Type resultType, Func<string, T

if (type.IsGenericType && resultType.IsGenericType)
{
var addOperation = FirstMethod(resultType, m => m.Name == "Add" && m.GetParameters().Count() == 1);
if (addOperation is object)
handlerCacheLock.EnterUpgradeableReadLock();
try
{
var addableResult = Activator.CreateInstance(resultType);
var addInvoker = MethodInvoker.GetHandler(addOperation);
var newElementType = resultType.GetGenericArguments()[0];
var i = 0;
foreach (var element in source as IEnumerable)
if (!handlerCache.TryGetValue(resultType, out FastInvokeHandler addInvoker))
{
var addOperation = FirstMethod(resultType, m => m.Name == "Add" && m.GetParameters().Length == 1);
if (addOperation is object)
{
addInvoker = MethodInvoker.GetHandler(addOperation);
}
handlerCacheLock.EnterWriteLock();
try
{
handlerCache[resultType] = addInvoker;
}
finally
{
handlerCacheLock.ExitWriteLock();
}
}
if (addInvoker != null)
{
var iStr = (i++).ToString();
var path = pathRoot.Length > 0 ? pathRoot + "." + iStr : iStr;
var newElement = MakeDeepCopy(element, newElementType, processor, path);
_ = addInvoker(addableResult, new object[] { newElement });
var addableResult = Activator.CreateInstance(resultType);
var newElementType = resultType.GetGenericArguments()[0];
var i = 0;
foreach (var element in source as IEnumerable)
{
var iStr = (i++).ToString();
var path = pathRoot.Length > 0 ? pathRoot + "." + iStr : iStr;
var newElement = MakeDeepCopy(element, newElementType, processor, path);
_ = addInvoker(addableResult, new object[] { newElement });
}
return addableResult;
}
return addableResult;
}

// TODO: add dictionaries support
// maybe use methods in Dictionary<KeyValuePair<TKey,TVal>>
finally
{
handlerCacheLock.ExitUpgradeableReadLock();
}
}

if (type.IsArray && resultType.IsArray)
Expand Down

0 comments on commit 2f5c171

Please sign in to comment.