summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs2
-rw-r--r--src/StardewModdingAPI/Framework/SContentManager.cs94
-rw-r--r--src/StardewModdingAPI/Program.cs26
3 files changed, 79 insertions, 43 deletions
diff --git a/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs b/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs
index e94d309e..ffa78ff6 100644
--- a/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs
+++ b/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs
index 0854c379..9e086870 100644
--- a/src/StardewModdingAPI/Framework/SContentManager.cs
+++ b/src/StardewModdingAPI/Framework/SContentManager.cs
@@ -1,8 +1,9 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Reflection;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using StardewModdingAPI.AssemblyRewriters;
@@ -96,33 +97,6 @@ namespace StardewModdingAPI.Framework
}
- /// <summary>Get the locale codes (like <c>ja-JP</c>) used in asset keys.</summary>
- /// <param name="reflection">Simplifies access to private game code.</param>
- private IDictionary<string, LanguageCode> GetKeyLocales(Reflector reflection)
- {
- // get the private code field directly to avoid changed-code logic
- IPrivateField<LanguageCode> codeField = reflection.GetPrivateField<LanguageCode>(typeof(LocalizedContentManager), "_currentLangCode");
-
- // remember previous settings
- LanguageCode previousCode = codeField.GetValue();
- string previousOverride = this.LanguageCodeOverride;
-
- // create locale => code map
- IDictionary<string, LanguageCode> map = new Dictionary<string, LanguageCode>(StringComparer.InvariantCultureIgnoreCase);
- this.LanguageCodeOverride = null;
- foreach (LanguageCode code in Enum.GetValues(typeof(LanguageCode)))
- {
- codeField.SetValue(code);
- map[this.GetKeyLocale.Invoke<string>()] = code;
- }
-
- // restore previous settings
- codeField.SetValue(previousCode);
- this.LanguageCodeOverride = previousOverride;
-
- return map;
- }
-
/// <summary>Normalise path separators in a file path. For asset keys, see <see cref="NormaliseAssetName"/> instead.</summary>
/// <param name="path">The file path to normalise.</param>
public string NormalisePathSeparators(string path)
@@ -209,10 +183,39 @@ namespace StardewModdingAPI.Framework
return GetAllAssetKeys().Distinct();
}
- /// <summary>Reset the asset cache and reload the game's static assets.</summary>
+ /// <summary>Purge assets from the cache that match one of the interceptors.</summary>
+ /// <param name="editors">The asset editors for which to purge matching assets.</param>
+ /// <param name="loaders">The asset loaders for which to purge matching assets.</param>
+ /// <returns>Returns whether any cache entries were invalidated.</returns>
+ public bool InvalidateCacheFor(IAssetEditor[] editors, IAssetLoader[] loaders)
+ {
+ if (!editors.Any() && !loaders.Any())
+ return false;
+
+ // get CanEdit/Load methods
+ MethodInfo canEdit = typeof(IAssetEditor).GetMethod(nameof(IAssetEditor.CanEdit));
+ MethodInfo canLoad = typeof(IAssetLoader).GetMethod(nameof(IAssetLoader.CanLoad));
+
+ // invalidate matching keys
+ return this.InvalidateCache((assetName, assetType) =>
+ {
+ // get asset metadata
+ IAssetInfo info = new AssetInfo(this.GetLocale(), assetName, assetType, this.NormaliseAssetName);
+
+ // check loaders
+ MethodInfo canLoadGeneric = canLoad.MakeGenericMethod(assetType);
+ if (loaders.Any(loader => (bool)canLoadGeneric.Invoke(loader, new object[] { info })))
+ return true;
+
+ // check editors
+ MethodInfo canEditGeneric = canEdit.MakeGenericMethod(assetType);
+ return editors.Any(editor => (bool)canEditGeneric.Invoke(editor, new object[] { info }));
+ });
+ }
+
+ /// <summary>Purge matched assets from the cache.</summary>
/// <param name="predicate">Matches the asset keys to invalidate.</param>
/// <returns>Returns whether any cache entries were invalidated.</returns>
- /// <remarks>This implementation is derived from <see cref="Game1.LoadContent"/>.</remarks>
public bool InvalidateCache(Func<string, Type, bool> predicate)
{
// find matching asset keys
@@ -220,7 +223,7 @@ namespace StardewModdingAPI.Framework
HashSet<string> purgeAssetKeys = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
foreach (string cacheKey in this.Cache.Keys)
{
- this.ParseCacheKey(cacheKey, out string assetKey, out string localeCode);
+ this.ParseCacheKey(cacheKey, out string assetKey, out _);
Type type = this.Cache[cacheKey].GetType();
if (predicate(assetKey, type))
{
@@ -237,7 +240,7 @@ namespace StardewModdingAPI.Framework
int reloaded = 0;
foreach (string key in purgeAssetKeys)
{
- if(this.CoreAssets.ReloadForKey(this, key))
+ if (this.CoreAssets.ReloadForKey(this, key))
reloaded++;
}
@@ -263,6 +266,33 @@ namespace StardewModdingAPI.Framework
|| this.Cache.ContainsKey($"{normalisedAssetName}.{this.GetKeyLocale.Invoke<string>()}"); // translated asset
}
+ /// <summary>Get the locale codes (like <c>ja-JP</c>) used in asset keys.</summary>
+ /// <param name="reflection">Simplifies access to private game code.</param>
+ private IDictionary<string, LanguageCode> GetKeyLocales(Reflector reflection)
+ {
+ // get the private code field directly to avoid changed-code logic
+ IPrivateField<LanguageCode> codeField = reflection.GetPrivateField<LanguageCode>(typeof(LocalizedContentManager), "_currentLangCode");
+
+ // remember previous settings
+ LanguageCode previousCode = codeField.GetValue();
+ string previousOverride = this.LanguageCodeOverride;
+
+ // create locale => code map
+ IDictionary<string, LanguageCode> map = new Dictionary<string, LanguageCode>(StringComparer.InvariantCultureIgnoreCase);
+ this.LanguageCodeOverride = null;
+ foreach (LanguageCode code in Enum.GetValues(typeof(LanguageCode)))
+ {
+ codeField.SetValue(code);
+ map[this.GetKeyLocale.Invoke<string>()] = code;
+ }
+
+ // restore previous settings
+ codeField.SetValue(previousCode);
+ this.LanguageCodeOverride = previousOverride;
+
+ return map;
+ }
+
/// <summary>Parse a cache key into its component parts.</summary>
/// <param name="cacheKey">The input cache key.</param>
/// <param name="assetKey">The original asset key.</param>
diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs
index 0e1930ac..79f8e801 100644
--- a/src/StardewModdingAPI/Program.cs
+++ b/src/StardewModdingAPI/Program.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
@@ -806,33 +806,39 @@ namespace StardewModdingAPI
}
}
- // reset cache when needed
- // only register listeners after Entry to avoid repeatedly reloading assets during load
+ // invalidate cache entries when needed
+ // (These listeners are registered after Entry to avoid repeatedly reloading assets as mods initialise.)
foreach (IModMetadata metadata in loadedMods)
{
if (metadata.Mod.Helper.Content is ContentHelper helper)
{
- // TODO: optimise by only reloading assets the new editors/loaders can intercept
helper.ObservableAssetEditors.CollectionChanged += (sender, e) =>
{
if (e.NewItems.Count > 0)
{
- this.Monitor.Log("Detected new asset editor, resetting cache...", LogLevel.Trace);
- this.ContentManager.InvalidateCache((key, type) => true);
+ this.Monitor.Log("Invalidating cache entries for new asset editors...", LogLevel.Trace);
+ this.ContentManager.InvalidateCacheFor(e.NewItems.Cast<IAssetEditor>().ToArray(), new IAssetLoader[0]);
}
};
helper.ObservableAssetLoaders.CollectionChanged += (sender, e) =>
{
if (e.NewItems.Count > 0)
{
- this.Monitor.Log("Detected new asset loader, resetting cache...", LogLevel.Trace);
- this.ContentManager.InvalidateCache((key, type) => true);
+ this.Monitor.Log("Invalidating cache entries for new asset loaders...", LogLevel.Trace);
+ this.ContentManager.InvalidateCacheFor(new IAssetEditor[0], e.NewItems.Cast<IAssetLoader>().ToArray());
}
};
}
}
- this.Monitor.Log("Resetting cache to enable interception...", LogLevel.Trace);
- this.ContentManager.InvalidateCache((key, type) => true);
+
+ // reset cache now if any editors or loaders were added during entry
+ IAssetEditor[] editors = loadedMods.SelectMany(p => p.Mod.Helper.Content.AssetEditors).ToArray();
+ IAssetLoader[] loaders = loadedMods.SelectMany(p => p.Mod.Helper.Content.AssetLoaders).ToArray();
+ if (editors.Any() || loaders.Any())
+ {
+ this.Monitor.Log("Invalidating cached assets for new editors & loaders...", LogLevel.Trace);
+ this.ContentManager.InvalidateCacheFor(editors, loaders);
+ }
}
/// <summary>Reload translations for all mods.</summary>