summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/Reflection
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-31 18:32:23 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-31 18:32:23 -0400
commit9992915f565578949cad8d9bb8ceb360e0db5c85 (patch)
tree1ff34ba24733fcdf44f52fb8ee10b2a956f1a708 /src/SMAPI/Framework/Reflection
parent9ef3f7edb1589a52794c7da7075996d4a02de6e7 (diff)
downloadSMAPI-9992915f565578949cad8d9bb8ceb360e0db5c85.tar.gz
SMAPI-9992915f565578949cad8d9bb8ceb360e0db5c85.tar.bz2
SMAPI-9992915f565578949cad8d9bb8ceb360e0db5c85.zip
replace MemoryCache with custom cache
This was causing significant frame stutters for some players since the migration to .NET 5 in Stardew Valley 1.5.5.
Diffstat (limited to 'src/SMAPI/Framework/Reflection')
-rw-r--r--src/SMAPI/Framework/Reflection/CacheEntry.cs30
-rw-r--r--src/SMAPI/Framework/Reflection/Reflector.cs110
2 files changed, 58 insertions, 82 deletions
diff --git a/src/SMAPI/Framework/Reflection/CacheEntry.cs b/src/SMAPI/Framework/Reflection/CacheEntry.cs
deleted file mode 100644
index 27f48a1f..00000000
--- a/src/SMAPI/Framework/Reflection/CacheEntry.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Reflection;
-
-namespace StardewModdingAPI.Framework.Reflection
-{
- /// <summary>A cached member reflection result.</summary>
- internal readonly struct CacheEntry
- {
- /*********
- ** Accessors
- *********/
- /// <summary>Whether the lookup found a valid match.</summary>
- [MemberNotNullWhen(true, nameof(CacheEntry.MemberInfo))]
- public bool IsValid => this.MemberInfo != null;
-
- /// <summary>The reflection data for this member (or <c>null</c> if invalid).</summary>
- public MemberInfo? MemberInfo { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="memberInfo">The reflection data for this member (or <c>null</c> if invalid).</param>
- public CacheEntry(MemberInfo? memberInfo)
- {
- this.MemberInfo = memberInfo;
- }
- }
-}
diff --git a/src/SMAPI/Framework/Reflection/Reflector.cs b/src/SMAPI/Framework/Reflection/Reflector.cs
index 79575c26..502a8519 100644
--- a/src/SMAPI/Framework/Reflection/Reflector.cs
+++ b/src/SMAPI/Framework/Reflection/Reflector.cs
@@ -1,6 +1,6 @@
using System;
using System.Reflection;
-using System.Runtime.Caching;
+using StardewModdingAPI.Framework.Utilities;
namespace StardewModdingAPI.Framework.Reflection
{
@@ -12,10 +12,7 @@ namespace StardewModdingAPI.Framework.Reflection
** Fields
*********/
/// <summary>The cached fields and methods found via reflection.</summary>
- private readonly MemoryCache Cache = new(typeof(Reflector).FullName!);
-
- /// <summary>The sliding cache expiration time.</summary>
- private readonly TimeSpan SlidingCacheExpiry = TimeSpan.FromMinutes(5);
+ private readonly IntervalMemoryCache<string, MemberInfo?> Cache = new();
/*********
@@ -136,6 +133,15 @@ namespace StardewModdingAPI.Framework.Reflection
return method!;
}
+ /****
+ ** Management
+ ****/
+ /// <summary>Start a new cache interval, clearing stale reflection lookups.</summary>
+ public void NewCacheInterval()
+ {
+ this.Cache.StartNewInterval();
+ }
+
/*********
** Private methods
@@ -149,20 +155,23 @@ namespace StardewModdingAPI.Framework.Reflection
private IReflectedField<TValue>? GetFieldFromHierarchy<TValue>(Type type, object? obj, string name, BindingFlags bindingFlags)
{
bool isStatic = bindingFlags.HasFlag(BindingFlags.Static);
- FieldInfo? field = this.GetCached($"field::{isStatic}::{type.FullName}::{name}", () =>
- {
- for (Type? curType = type; curType != null; curType = curType.BaseType)
+ FieldInfo? field = this.GetCached(
+ 'f', type, name, isStatic,
+ fetch: () =>
{
- FieldInfo? fieldInfo = curType.GetField(name, bindingFlags);
- if (fieldInfo != null)
+ for (Type? curType = type; curType != null; curType = curType.BaseType)
{
- type = curType;
- return fieldInfo;
+ FieldInfo? fieldInfo = curType.GetField(name, bindingFlags);
+ if (fieldInfo != null)
+ {
+ type = curType;
+ return fieldInfo;
+ }
}
- }
- return null;
- });
+ return null;
+ }
+ );
return field != null
? new ReflectedField<TValue>(type, obj, field, isStatic)
@@ -178,20 +187,23 @@ namespace StardewModdingAPI.Framework.Reflection
private IReflectedProperty<TValue>? GetPropertyFromHierarchy<TValue>(Type type, object? obj, string name, BindingFlags bindingFlags)
{
bool isStatic = bindingFlags.HasFlag(BindingFlags.Static);
- PropertyInfo? property = this.GetCached<PropertyInfo>($"property::{isStatic}::{type.FullName}::{name}", () =>
- {
- for (Type? curType = type; curType != null; curType = curType.BaseType)
+ PropertyInfo? property = this.GetCached(
+ 'p', type, name, isStatic,
+ fetch: () =>
{
- PropertyInfo? propertyInfo = curType.GetProperty(name, bindingFlags);
- if (propertyInfo != null)
+ for (Type? curType = type; curType != null; curType = curType.BaseType)
{
- type = curType;
- return propertyInfo;
+ PropertyInfo? propertyInfo = curType.GetProperty(name, bindingFlags);
+ if (propertyInfo != null)
+ {
+ type = curType;
+ return propertyInfo;
+ }
}
- }
- return null;
- });
+ return null;
+ }
+ );
return property != null
? new ReflectedProperty<TValue>(type, obj, property, isStatic)
@@ -206,47 +218,41 @@ namespace StardewModdingAPI.Framework.Reflection
private IReflectedMethod? GetMethodFromHierarchy(Type type, object? obj, string name, BindingFlags bindingFlags)
{
bool isStatic = bindingFlags.HasFlag(BindingFlags.Static);
- MethodInfo? method = this.GetCached($"method::{isStatic}::{type.FullName}::{name}", () =>
- {
- for (Type? curType = type; curType != null; curType = curType.BaseType)
+ MethodInfo? method = this.GetCached(
+ 'm', type, name, isStatic,
+ fetch: () =>
{
- MethodInfo? methodInfo = curType.GetMethod(name, bindingFlags);
- if (methodInfo != null)
+ for (Type? curType = type; curType != null; curType = curType.BaseType)
{
- type = curType;
- return methodInfo;
+ MethodInfo? methodInfo = curType.GetMethod(name, bindingFlags);
+ if (methodInfo != null)
+ {
+ type = curType;
+ return methodInfo;
+ }
}
- }
- return null;
- });
+ return null;
+ }
+ );
return method != null
- ? new ReflectedMethod(type, obj, method, isStatic: bindingFlags.HasFlag(BindingFlags.Static))
+ ? new ReflectedMethod(type, obj, method, isStatic: 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="memberType">A letter representing the member type (like 'm' for method).</param>
+ /// <param name="type">The type whose members are being reflected.</param>
+ /// <param name="memberName">The member name.</param>
+ /// <param name="isStatic">Whether the member is static.</param>
/// <param name="fetch">Fetches a new value to cache.</param>
- private TMemberInfo? GetCached<TMemberInfo>(string key, Func<TMemberInfo?> fetch)
+ private TMemberInfo? GetCached<TMemberInfo>(char memberType, Type type, string memberName, bool isStatic, Func<TMemberInfo?> fetch)
where TMemberInfo : MemberInfo
{
- // get from cache
- if (this.Cache.Contains(key))
- {
- CacheEntry entry = (CacheEntry)this.Cache[key];
- return entry.IsValid
- ? (TMemberInfo)entry.MemberInfo
- : default;
- }
-
- // fetch & cache new value
- TMemberInfo? result = fetch();
- CacheEntry cacheEntry = new(result);
- this.Cache.Add(key, cacheEntry, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry });
- return result;
+ string key = $"{memberType}{(isStatic ? 's' : 'i')}{type.FullName}:{memberName}";
+ return (TMemberInfo?)this.Cache.GetOrSet(key, fetch);
}
}
}