Merge branch 'develop' into stable
This commit is contained in:
commit
e10147e7bd
|
@ -1,7 +1,7 @@
|
|||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<!--set general build properties -->
|
||||
<Version>3.14.6</Version>
|
||||
<Version>3.14.7</Version>
|
||||
<Product>SMAPI</Product>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
||||
|
@ -69,8 +69,10 @@
|
|||
<Copy SourceFiles="$(TargetDir)\MonoMod.Common.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
|
||||
<!-- .NET dependencies -->
|
||||
<Copy SourceFiles="$(TargetDir)\System.Configuration.ConfigurationManager.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\System.Management.dll" DestinationFolder="$(GamePath)\smapi-internal" Condition="$(OS) == 'Windows_NT'" />
|
||||
|
||||
<!-- Legacy .NET dependencies (remove in SMAPI 4.0.0) -->
|
||||
<Copy SourceFiles="$(TargetDir)\System.Configuration.ConfigurationManager.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\System.Runtime.Caching.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\System.Security.Permissions.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
</Target>
|
||||
|
|
|
@ -142,19 +142,20 @@ for folder in ${folders[@]}; do
|
|||
cp "$smapiBin/SMAPI.metadata.json" "$bundlePath/smapi-internal/metadata.json"
|
||||
if [ $folder == "linux" ] || [ $folder == "macOS" ]; then
|
||||
cp "$installAssets/unix-launcher.sh" "$bundlePath"
|
||||
cp "$smapiBin/System.Runtime.Caching.dll" "$bundlePath/smapi-internal"
|
||||
else
|
||||
cp "$installAssets/windows-exe-config.xml" "$bundlePath/StardewModdingAPI.exe.config"
|
||||
fi
|
||||
|
||||
# copy .NET dependencies
|
||||
cp "$smapiBin/System.Configuration.ConfigurationManager.dll" "$bundlePath/smapi-internal"
|
||||
cp "$smapiBin/System.Runtime.Caching.dll" "$bundlePath/smapi-internal"
|
||||
cp "$smapiBin/System.Security.Permissions.dll" "$bundlePath/smapi-internal"
|
||||
if [ $folder == "windows" ]; then
|
||||
cp "$smapiBin/System.Management.dll" "$bundlePath/smapi-internal"
|
||||
fi
|
||||
|
||||
# copy legacy .NET dependencies (remove in SMAPI 4.0.0)
|
||||
cp "$smapiBin/System.Configuration.ConfigurationManager.dll" "$bundlePath/smapi-internal"
|
||||
cp "$smapiBin/System.Runtime.Caching.dll" "$bundlePath/smapi-internal"
|
||||
cp "$smapiBin/System.Security.Permissions.dll" "$bundlePath/smapi-internal"
|
||||
|
||||
# copy bundled mods
|
||||
for modName in ${bundleModNames[@]}; do
|
||||
fromPath="src/SMAPI.Mods.$modName/bin/$buildConfig/$runtime/publish"
|
||||
|
|
|
@ -162,20 +162,21 @@ foreach ($folder in $folders) {
|
|||
cp "$smapiBin/SMAPI.metadata.json" "$bundlePath/smapi-internal/metadata.json"
|
||||
if ($folder -eq "linux" -or $folder -eq "macOS") {
|
||||
cp "$installAssets/unix-launcher.sh" "$bundlePath"
|
||||
cp "$smapiBin/System.Runtime.Caching.dll" "$bundlePath/smapi-internal"
|
||||
}
|
||||
else {
|
||||
cp "$installAssets/windows-exe-config.xml" "$bundlePath/StardewModdingAPI.exe.config"
|
||||
}
|
||||
|
||||
# copy .NET dependencies
|
||||
cp "$smapiBin/System.Configuration.ConfigurationManager.dll" "$bundlePath/smapi-internal"
|
||||
cp "$smapiBin/System.Runtime.Caching.dll" "$bundlePath/smapi-internal"
|
||||
cp "$smapiBin/System.Security.Permissions.dll" "$bundlePath/smapi-internal"
|
||||
if ($folder -eq "windows") {
|
||||
cp "$smapiBin/System.Management.dll" "$bundlePath/smapi-internal"
|
||||
}
|
||||
|
||||
# copy legacy .NET dependencies (remove in SMAPI 4.0.0)
|
||||
cp "$smapiBin/System.Configuration.ConfigurationManager.dll" "$bundlePath/smapi-internal"
|
||||
cp "$smapiBin/System.Runtime.Caching.dll" "$bundlePath/smapi-internal"
|
||||
cp "$smapiBin/System.Security.Permissions.dll" "$bundlePath/smapi-internal"
|
||||
|
||||
# copy bundled mods
|
||||
foreach ($modName in $bundleModNames) {
|
||||
$fromPath = "src/SMAPI.Mods.$modName/bin/$buildConfig/$runtime/publish"
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
← [README](README.md)
|
||||
|
||||
# Release notes
|
||||
## 3.14.7
|
||||
Released 01 June 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Optimized reflection cache to reduce frame skips for some players.
|
||||
* For mod authors:
|
||||
* Removed `runtimeconfig.json` setting which impacted hot reload support.
|
||||
|
||||
## 3.14.6
|
||||
Released 27 May 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
|
|
|
@ -9,8 +9,9 @@
|
|||
}
|
||||
],
|
||||
"configProperties": {
|
||||
"System.Runtime.TieredCompilation": false,
|
||||
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false
|
||||
// disable tiered runtime JIT: https://github.com/dotnet/runtime/blob/main/docs/design/features/tiered-compilation.md
|
||||
// This is disabled by the base game, and causes issues with Harmony patches.
|
||||
"System.Runtime.TieredCompilation": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Name": "Console Commands",
|
||||
"Author": "SMAPI",
|
||||
"Version": "3.14.6",
|
||||
"Version": "3.14.7",
|
||||
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
||||
"UniqueID": "SMAPI.ConsoleCommands",
|
||||
"EntryDll": "ConsoleCommands.dll",
|
||||
"MinimumApiVersion": "3.14.6"
|
||||
"MinimumApiVersion": "3.14.7"
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Name": "Error Handler",
|
||||
"Author": "SMAPI",
|
||||
"Version": "3.14.6",
|
||||
"Version": "3.14.7",
|
||||
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
|
||||
"UniqueID": "SMAPI.ErrorHandler",
|
||||
"EntryDll": "ErrorHandler.dll",
|
||||
"MinimumApiVersion": "3.14.6"
|
||||
"MinimumApiVersion": "3.14.7"
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Name": "Save Backup",
|
||||
"Author": "SMAPI",
|
||||
"Version": "3.14.6",
|
||||
"Version": "3.14.7",
|
||||
"Description": "Automatically backs up all your saves once per day into its folder.",
|
||||
"UniqueID": "SMAPI.SaveBackup",
|
||||
"EntryDll": "SaveBackup.dll",
|
||||
"MinimumApiVersion": "3.14.6"
|
||||
"MinimumApiVersion": "3.14.7"
|
||||
}
|
||||
|
|
|
@ -35,6 +35,15 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
|||
AccessesFilesystem = 128,
|
||||
|
||||
/// <summary>Uses .NET APIs for shell or process access.</summary>
|
||||
AccessesShell = 256
|
||||
AccessesShell = 256,
|
||||
|
||||
/// <summary>References the legacy <c>System.Configuration.ConfigurationManager</c> assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.</summary>
|
||||
DetectedLegacyConfigurationDll = 512,
|
||||
|
||||
/// <summary>References the legacy <c>System.Runtime.Caching</c> assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.</summary>
|
||||
DetectedLegacyCachingDll = 1024,
|
||||
|
||||
/// <summary>References the legacy <c>System.Security.Permissions</c> assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.</summary>
|
||||
DetectedLegacyPermissionsDll = 2048
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace StardewModdingAPI
|
|||
internal static int? LogScreenId { get; set; }
|
||||
|
||||
/// <summary>SMAPI's current raw semantic version.</summary>
|
||||
internal static string RawApiVersion = "3.14.6";
|
||||
internal static string RawApiVersion = "3.14.7";
|
||||
}
|
||||
|
||||
/// <summary>Contains SMAPI's constants and assumptions.</summary>
|
||||
|
|
|
@ -88,6 +88,10 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="warning">The warning to set.</param>
|
||||
IModMetadata SetWarning(ModWarning warning);
|
||||
|
||||
/// <summary>Remove a warning flag for the mod.</summary>
|
||||
/// <param name="warning">The warning to remove.</param>
|
||||
IModMetadata RemoveWarning(ModWarning warning);
|
||||
|
||||
/// <summary>Set the mod instance.</summary>
|
||||
/// <param name="mod">The mod instance to set.</param>
|
||||
/// <param name="translations">The translations for this mod (if loaded).</param>
|
||||
|
|
|
@ -163,6 +163,29 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
this.AssemblyDefinitionResolver.Add(assembly.Definition);
|
||||
}
|
||||
|
||||
// special case: clear legacy-DLL warnings if the mod bundles a copy
|
||||
if (mod.Warnings.HasFlag(ModWarning.DetectedLegacyCachingDll))
|
||||
{
|
||||
if (File.Exists(Path.Combine(mod.DirectoryPath, "System.Runtime.Caching.dll")))
|
||||
mod.RemoveWarning(ModWarning.DetectedLegacyCachingDll);
|
||||
else
|
||||
{
|
||||
// remove duplicate warnings (System.Runtime.Caching.dll references these)
|
||||
mod.RemoveWarning(ModWarning.DetectedLegacyConfigurationDll);
|
||||
mod.RemoveWarning(ModWarning.DetectedLegacyPermissionsDll);
|
||||
}
|
||||
}
|
||||
if (mod.Warnings.HasFlag(ModWarning.DetectedLegacyConfigurationDll))
|
||||
{
|
||||
if (File.Exists(Path.Combine(mod.DirectoryPath, "System.Configuration.ConfigurationManager.dll")))
|
||||
mod.RemoveWarning(ModWarning.DetectedLegacyConfigurationDll);
|
||||
}
|
||||
if (mod.Warnings.HasFlag(ModWarning.DetectedLegacyPermissionsDll))
|
||||
{
|
||||
if (File.Exists(Path.Combine(mod.DirectoryPath, "System.Security.Permissions.dll")))
|
||||
mod.RemoveWarning(ModWarning.DetectedLegacyPermissionsDll);
|
||||
}
|
||||
|
||||
// throw if incompatibilities detected
|
||||
if (!assumeCompatible && mod.Warnings.HasFlag(ModWarning.BrokenCodeLoaded))
|
||||
throw new IncompatibleInstructionException();
|
||||
|
@ -429,6 +452,21 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
mod.SetWarning(ModWarning.AccessesShell);
|
||||
break;
|
||||
|
||||
case InstructionHandleResult.DetectedLegacyCachingDll:
|
||||
template = $"{logPrefix}Detected reference to System.Runtime.Caching.dll, which will be removed in SMAPI 4.0.0.";
|
||||
mod.SetWarning(ModWarning.DetectedLegacyCachingDll);
|
||||
break;
|
||||
|
||||
case InstructionHandleResult.DetectedLegacyConfigurationDll:
|
||||
template = $"{logPrefix}Detected reference to System.Configuration.ConfigurationManager.dll, which will be removed in SMAPI 4.0.0.";
|
||||
mod.SetWarning(ModWarning.DetectedLegacyConfigurationDll);
|
||||
break;
|
||||
|
||||
case InstructionHandleResult.DetectedLegacyPermissionsDll:
|
||||
template = $"{logPrefix}Detected reference to System.Security.Permissions.dll, which will be removed in SMAPI 4.0.0.";
|
||||
mod.SetWarning(ModWarning.DetectedLegacyPermissionsDll);
|
||||
break;
|
||||
|
||||
case InstructionHandleResult.None:
|
||||
break;
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
using Mono.Cecil;
|
||||
using StardewModdingAPI.Framework.ModLoading.Framework;
|
||||
|
||||
namespace StardewModdingAPI.Framework.ModLoading.Finders
|
||||
{
|
||||
/// <summary>Detects assembly references which will break in SMAPI 4.0.0.</summary>
|
||||
internal class LegacyAssemblyFinder : BaseInstructionHandler
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public LegacyAssemblyFinder()
|
||||
: base(defaultPhrase: "legacy assembly references") { }
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Handle(ModuleDefinition module)
|
||||
{
|
||||
foreach (AssemblyNameReference assembly in module.AssemblyReferences)
|
||||
{
|
||||
InstructionHandleResult flag = this.GetFlag(assembly);
|
||||
if (flag is InstructionHandleResult.None)
|
||||
continue;
|
||||
|
||||
this.MarkFlag(flag);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Get the instruction handle flag for the given assembly reference, if any.</summary>
|
||||
/// <param name="assemblyRef">The assembly reference.</param>
|
||||
private InstructionHandleResult GetFlag(AssemblyNameReference assemblyRef)
|
||||
{
|
||||
return assemblyRef.Name switch
|
||||
{
|
||||
"System.Configuration.ConfigurationManager" => InstructionHandleResult.DetectedLegacyConfigurationDll,
|
||||
"System.Runtime.Caching" => InstructionHandleResult.DetectedLegacyCachingDll,
|
||||
"System.Security.Permission" => InstructionHandleResult.DetectedLegacyPermissionsDll,
|
||||
_ => InstructionHandleResult.None
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,6 +30,15 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
DetectedFilesystemAccess,
|
||||
|
||||
/// <summary>The instruction accesses the OS shell or processes directly.</summary>
|
||||
DetectedShellAccess
|
||||
DetectedShellAccess,
|
||||
|
||||
/// <summary>The module references the legacy <c>System.Configuration.ConfigurationManager</c> assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.</summary>
|
||||
DetectedLegacyConfigurationDll,
|
||||
|
||||
/// <summary>The module references the legacy <c>System.Runtime.Caching</c> assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.</summary>
|
||||
DetectedLegacyCachingDll,
|
||||
|
||||
/// <summary>The module references the legacy <c>System.Security.Permissions</c> assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.</summary>
|
||||
DetectedLegacyPermissionsDll
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,6 +138,13 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IModMetadata RemoveWarning(ModWarning warning)
|
||||
{
|
||||
this.ActualWarnings &= ~warning;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IModMetadata SetMod(IMod mod, TranslationHelper translations)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1164,6 +1164,8 @@ namespace StardewModdingAPI.Framework
|
|||
protected void OnNewDayAfterFade()
|
||||
{
|
||||
this.EventManager.DayEnding.RaiseEmpty();
|
||||
|
||||
this.Reflection.NewCacheInterval();
|
||||
}
|
||||
|
||||
/// <summary>A callback invoked after an asset is fully loaded through a content manager.</summary>
|
||||
|
@ -1677,6 +1679,31 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
#pragma warning restore CS0612, CS0618
|
||||
|
||||
// log deprecation warnings
|
||||
if (metadata.HasWarnings(ModWarning.DetectedLegacyCachingDll, ModWarning.DetectedLegacyConfigurationDll, ModWarning.DetectedLegacyPermissionsDll))
|
||||
{
|
||||
string?[] referenced =
|
||||
new[]
|
||||
{
|
||||
metadata.Warnings.HasFlag(ModWarning.DetectedLegacyConfigurationDll) ? "System.Configuration.ConfigurationManager" : null,
|
||||
metadata.Warnings.HasFlag(ModWarning.DetectedLegacyCachingDll) ? "System.Runtime.Caching" : null,
|
||||
metadata.Warnings.HasFlag(ModWarning.DetectedLegacyPermissionsDll) ? "System.Security.Permissions" : null
|
||||
}
|
||||
.Where(p => p is not null)
|
||||
.ToArray();
|
||||
|
||||
foreach (string? name in referenced)
|
||||
{
|
||||
DeprecationManager.Warn(
|
||||
metadata,
|
||||
$"using {name} without bundling it",
|
||||
"3.14.7",
|
||||
DeprecationLevel.Notice,
|
||||
logStackTrace: false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// call entry method
|
||||
Context.HeuristicModsRunningCode.Push(metadata);
|
||||
try
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Utilities
|
||||
{
|
||||
/// <summary>A memory cache with sliding expiry based on custom intervals, with no background processing.</summary>
|
||||
/// <typeparam name="TKey">The cache key type.</typeparam>
|
||||
/// <typeparam name="TValue">The cache value type.</typeparam>
|
||||
/// <remarks>This is optimized for small caches that are reset relatively rarely. Each cache entry is marked as hot (accessed since the interval started) or stale.
|
||||
/// When a new interval is started, stale entries are cleared and hot entries become stale.</remarks>
|
||||
internal class IntervalMemoryCache<TKey, TValue>
|
||||
where TKey : notnull
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The cached values that were accessed during the current interval.</summary>
|
||||
private Dictionary<TKey, TValue> HotCache = new();
|
||||
|
||||
/// <summary>The cached values that will expire on the next interval.</summary>
|
||||
private Dictionary<TKey, TValue> StaleCache = new();
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get a value from the cache, fetching it first if needed.</summary>
|
||||
/// <param name="cacheKey">The unique key for the cached value.</param>
|
||||
/// <param name="get">Get the latest data if it's not in the cache yet.</param>
|
||||
public TValue GetOrSet(TKey cacheKey, Func<TValue> get)
|
||||
{
|
||||
// from hot cache
|
||||
if (this.HotCache.TryGetValue(cacheKey, out TValue? value))
|
||||
return value;
|
||||
|
||||
// from stale cache
|
||||
if (this.StaleCache.TryGetValue(cacheKey, out value))
|
||||
{
|
||||
this.HotCache[cacheKey] = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
// new value
|
||||
value = get();
|
||||
this.HotCache[cacheKey] = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>Start a new cache interval, removing any stale entries.</summary>
|
||||
public void StartNewInterval()
|
||||
{
|
||||
this.StaleCache.Clear();
|
||||
if (this.HotCache.Count is not 0)
|
||||
(this.StaleCache, this.HotCache) = (this.HotCache, this.StaleCache); // swap hot cache to stale
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,6 +53,9 @@ namespace StardewModdingAPI.Metadata
|
|||
|
||||
// detect Harmony & rewrite for SMAPI 3.12 (Harmony 1.x => 2.0 update)
|
||||
yield return new HarmonyRewriter();
|
||||
|
||||
// detect issues for SMAPI 4.0.0
|
||||
yield return new LegacyAssemblyFinder();
|
||||
}
|
||||
else
|
||||
yield return new HarmonyRewriter(shouldRewrite: false);
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
<PackageReference Include="Pintail" Version="2.1.0" />
|
||||
<PackageReference Include="Platonymous.TMXTile" Version="1.5.9" />
|
||||
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
|
||||
|
||||
<!-- legacy package; remove in SMAPI 4.0.0 -->
|
||||
<PackageReference Include="System.Runtime.Caching" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
Loading…
Reference in New Issue