diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-05-31 18:32:23 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-05-31 18:32:23 -0400 |
commit | 9992915f565578949cad8d9bb8ceb360e0db5c85 (patch) | |
tree | 1ff34ba24733fcdf44f52fb8ee10b2a956f1a708 /src/SMAPI/Framework/Reflection | |
parent | 9ef3f7edb1589a52794c7da7075996d4a02de6e7 (diff) | |
download | SMAPI-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.cs | 30 | ||||
-rw-r--r-- | src/SMAPI/Framework/Reflection/Reflector.cs | 110 |
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); } } } |