diff options
author | Jesse Plamondon-Willard <github@jplamondonw.com> | 2016-12-09 14:15:14 -0500 |
---|---|---|
committer | Jesse Plamondon-Willard <github@jplamondonw.com> | 2016-12-09 14:15:14 -0500 |
commit | 80b6e208418b7d9237bc4aa98c68d2bb849b49d5 (patch) | |
tree | a06c7646bf091fc4c6022c7d2b708ea4e4cf7e61 /src/StardewModdingAPI | |
parent | cd0e5961d454e5861e2fd760388eb6920a1e2257 (diff) | |
download | SMAPI-80b6e208418b7d9237bc4aa98c68d2bb849b49d5.tar.gz SMAPI-80b6e208418b7d9237bc4aa98c68d2bb849b49d5.tar.bz2 SMAPI-80b6e208418b7d9237bc4aa98c68d2bb849b49d5.zip |
cache reflection lookups with sliding expiry (#185)
Diffstat (limited to 'src/StardewModdingAPI')
-rw-r--r-- | src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs | 67 | ||||
-rw-r--r-- | src/StardewModdingAPI/StardewModdingAPI.csproj | 1 |
2 files changed, 56 insertions, 12 deletions
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,13 +1,26 @@ using System; +using System.Linq; using System.Reflection; +using System.Runtime.Caching; using StardewModdingAPI.Reflection; namespace StardewModdingAPI.Framework.Reflection { /// <summary>Provides helper methods for accessing private game code.</summary> + /// <remarks>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).</remarks> internal class ReflectionHelper : IReflectionHelper { /********* + ** Properties + *********/ + /// <summary>The cached fields and methods found via reflection.</summary> + private readonly MemoryCache Cache = new MemoryCache(typeof(ReflectionHelper).FullName); + + /// <summary>The sliding cache expiration time.</summary> + private readonly TimeSpan SlidingCacheExpiry = TimeSpan.FromMinutes(5); + + + /********* ** Public methods *********/ /**** @@ -152,12 +165,17 @@ namespace StardewModdingAPI.Framework.Reflection /// <param name="bindingFlags">The reflection binding which flags which indicates what type of field to find.</param> private IPrivateField<TValue> GetFieldFromHierarchy<TValue>(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<FieldInfo>($"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<TValue>(type, obj, field, isStatic: bindingFlags.HasFlag(BindingFlags.Static)) + ? new PrivateField<TValue>(type, obj, field, isStatic) : null; } @@ -168,9 +186,14 @@ namespace StardewModdingAPI.Framework.Reflection /// <param name="bindingFlags">The reflection binding which flags which indicates what type of method to find.</param> 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 /// <param name="argumentTypes">The argument types of the method signature to find.</param> 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; } + + /// <summary>Get a method or field through the cache.</summary> + /// <typeparam name="TMemberInfo">The expected <see cref="MemberInfo"/> type.</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="fetch">Fetches a new value to cache.</param> + private TMemberInfo GetCached<TMemberInfo>(string key, Func<TMemberInfo> 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 diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 59edc0c9..6e87fbdb 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -106,6 +106,7 @@ <Reference Include="System.Numerics"> <Private>True</Private> </Reference> + <Reference Include="System.Runtime.Caching" /> <Reference Include="System.Windows.Forms" Condition="$(OS) == 'Windows_NT'" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> |