From 80b6e208418b7d9237bc4aa98c68d2bb849b49d5 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 9 Dec 2016 14:15:14 -0500 Subject: cache reflection lookups with sliding expiry (#185) --- .../Framework/Reflection/ReflectionHelper.cs | 67 ++++++++++++++++++---- 1 file changed, 55 insertions(+), 12 deletions(-) (limited to 'src/StardewModdingAPI/Framework/Reflection') diff --git a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs index 17758a39..fd916bbe 100644 --- a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs +++ b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs @@ -1,12 +1,25 @@ using System; +using System.Linq; using System.Reflection; +using System.Runtime.Caching; using StardewModdingAPI.Reflection; namespace StardewModdingAPI.Framework.Reflection { /// Provides helper methods for accessing private game code. + /// This implementation searches up the type hierarchy, and caches the reflected fields and methods with a sliding expiry (to optimise performance without unnecessary memory usage). internal class ReflectionHelper : IReflectionHelper { + /********* + ** Properties + *********/ + /// The cached fields and methods found via reflection. + private readonly MemoryCache Cache = new MemoryCache(typeof(ReflectionHelper).FullName); + + /// The sliding cache expiration time. + private readonly TimeSpan SlidingCacheExpiry = TimeSpan.FromMinutes(5); + + /********* ** Public methods *********/ @@ -152,12 +165,17 @@ namespace StardewModdingAPI.Framework.Reflection /// The reflection binding which flags which indicates what type of field to find. private IPrivateField GetFieldFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) { - FieldInfo field = null; - for (; type != null && field == null; type = type.BaseType) - field = type.GetField(name, bindingFlags); + bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); + FieldInfo field = this.GetCached($"field::{isStatic}::{type.FullName}::{name}", () => + { + FieldInfo fieldInfo = null; + for (; type != null && fieldInfo == null; type = type.BaseType) + fieldInfo = type.GetField(name, bindingFlags); + return fieldInfo; + }); return field != null - ? new PrivateField(type, obj, field, isStatic: bindingFlags.HasFlag(BindingFlags.Static)) + ? new PrivateField(type, obj, field, isStatic) : null; } @@ -168,9 +186,14 @@ namespace StardewModdingAPI.Framework.Reflection /// The reflection binding which flags which indicates what type of method to find. private IPrivateMethod GetMethodFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) { - MethodInfo method = null; - for (; type != null && method == null; type = type.BaseType) - method = type.GetMethod(name, bindingFlags); + bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); + MethodInfo method = this.GetCached($"method::{isStatic}::{type.FullName}::{name}", () => + { + MethodInfo methodInfo = null; + for (; type != null && methodInfo == null; type = type.BaseType) + methodInfo = type.GetMethod(name, bindingFlags); + return methodInfo; + }); return method != null ? new PrivateMethod(type, obj, method, isStatic: bindingFlags.HasFlag(BindingFlags.Static)) @@ -185,13 +208,33 @@ namespace StardewModdingAPI.Framework.Reflection /// The argument types of the method signature to find. private PrivateMethod GetMethodFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags, Type[] argumentTypes) { - MethodInfo method = null; - for (; type != null && method == null; type = type.BaseType) - method = type.GetMethod(name, bindingFlags, null, argumentTypes, null); - + bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); + MethodInfo method = this.GetCached($"method::{isStatic}::{type.FullName}::{name}({string.Join(",", argumentTypes.Select(p => p.FullName))})", () => + { + MethodInfo methodInfo = null; + for (; type != null && methodInfo == null; type = type.BaseType) + methodInfo = type.GetMethod(name, bindingFlags, null, argumentTypes, null); + return methodInfo; + }); return method != null - ? new PrivateMethod(type, obj, method, isStatic: bindingFlags.HasFlag(BindingFlags.Static)) + ? new PrivateMethod(type, obj, method, isStatic) : null; } + + /// Get a method or field through the cache. + /// The expected type. + /// The cache key. + /// Fetches a new value to cache. + private TMemberInfo GetCached(string key, Func fetch) where TMemberInfo : MemberInfo + { + // get from cache + if (this.Cache.Contains(key)) + return (TMemberInfo)this.Cache[key]; + + // fetch & cache new value + TMemberInfo result = fetch(); + this.Cache.Add(key, result, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); + return result; + } } } \ No newline at end of file -- cgit