summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs36
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs27
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs2
-rw-r--r--src/SMAPI/Framework/ContentManagers/IContentManager.cs11
-rw-r--r--src/SMAPI/Framework/Utilities/TickCacheDictionary.cs26
5 files changed, 80 insertions, 22 deletions
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 2b13f57a..fc61b44b 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -151,8 +151,23 @@ namespace StardewModdingAPI.Framework
onAssetLoaded: onAssetLoaded
)
);
+
+ var contentManagerForAssetPropagation = new GameContentManagerForAssetPropagation(
+ name: nameof(GameContentManagerForAssetPropagation),
+ serviceProvider: serviceProvider,
+ rootDirectory: rootDirectory,
+ currentCulture: currentCulture,
+ coordinator: this,
+ monitor: monitor,
+ reflection: reflection,
+ onDisposing: this.OnDisposing,
+ onLoadingFirstAsset: onLoadingFirstAsset,
+ onAssetLoaded: onAssetLoaded
+ );
+ this.ContentManagers.Add(contentManagerForAssetPropagation);
+
this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory);
- this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, this.Monitor, reflection, name => this.ParseAssetName(name, allowLocales: true));
+ this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, contentManagerForAssetPropagation, this.Monitor, reflection, name => this.ParseAssetName(name, allowLocales: true));
this.LocaleCodes = new Lazy<Dictionary<string, LocalizedContentManager.LanguageCode>>(() => this.GetLocaleCodes(customLanguages: Enumerable.Empty<ModLanguage>()));
}
@@ -379,14 +394,31 @@ namespace StardewModdingAPI.Framework
// cached assets
foreach (IContentManager contentManager in this.ContentManagers)
{
- foreach ((string key, object asset) in contentManager.InvalidateCache((key, type) => predicate(contentManager, key, type), dispose))
+ foreach ((string key, object asset) in contentManager.GetCachedAssets())
{
+ if (!predicate(contentManager, key, asset.GetType()))
+ continue;
+
AssetName assetName = this.ParseAssetName(key, allowLocales: true);
+ contentManager.InvalidateCache(assetName, dispose);
+
if (!invalidatedAssets.ContainsKey(assetName))
invalidatedAssets[assetName] = asset.GetType();
}
}
+ // forget localized flags
+ // A mod might provide a localized variant of a normally non-localized asset (like
+ // `Maps/MovieTheater.fr-FR`). When the asset is invalidated, we need to recheck
+ // whether the asset is localized in case it stops providing it.
+ foreach (IAssetName assetName in invalidatedAssets.Keys)
+ {
+ LocalizedContentManager.localizedAssetNames.Remove(assetName.Name);
+
+ if (LocalizedContentManager.localizedAssetNames.TryGetValue(assetName.BaseName, out string? targetForBaseKey) && targetForBaseKey == assetName.Name)
+ LocalizedContentManager.localizedAssetNames.Remove(assetName.BaseName);
+ }
+
// special case: maps may be loaded through a temporary content manager that's removed while the map is still in use.
// This notably affects the town and farmhouse maps.
if (Game1.locations != null)
diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
index 575d252e..ddc02a8c 100644
--- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
@@ -231,24 +231,21 @@ namespace StardewModdingAPI.Framework.ContentManagers
** Cache invalidation
****/
/// <inheritdoc />
- public IDictionary<string, object> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
+ public IEnumerable<KeyValuePair<string, object>> GetCachedAssets()
{
- IDictionary<string, object> removeAssets = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
- this.Cache.Remove((key, asset) =>
- {
- string baseAssetName = this.Coordinator.ParseAssetName(key, allowLocales: this.TryLocalizeKeys).BaseName;
+ foreach (string key in this.Cache.Keys)
+ yield return new(key, this.Cache[key]);
+ }
- // check if asset should be removed
- bool remove = removeAssets.ContainsKey(baseAssetName);
- if (!remove && predicate(baseAssetName, asset.GetType()))
- {
- removeAssets[baseAssetName] = asset;
- remove = true;
- }
- return remove;
- }, dispose);
+ /// <inheritdoc />
+ public bool InvalidateCache(IAssetName assetName, bool dispose = false)
+ {
+ if (!this.Cache.ContainsKey(assetName.Name))
+ return false;
- return removeAssets;
+ // remove from cache
+ this.Cache.Remove(assetName.Name, dispose);
+ return true;
}
/// <inheritdoc />
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index 1c603f85..4390d472 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -75,7 +75,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
// custom asset from a loader
string locale = this.GetLocale();
IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName);
- AssetOperationGroup? operations = this.Coordinator.GetAssetOperations<object>(info);
+ AssetOperationGroup? operations = this.Coordinator.GetAssetOperations<T>(info);
if (operations?.LoadOperations.Count > 0)
{
if (!this.AssertMaxOneRequiredLoader(info, operations.LoadOperations, out string? error))
diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
index ac67cad5..f2e3b9f0 100644
--- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
@@ -32,7 +32,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <typeparam name="T">The expected asset type.</typeparam>
/// <param name="assetName">The normalized asset name.</param>
bool DoesAssetExist<T>(IAssetName assetName)
- where T: notnull;
+ where T : notnull;
/// <summary>Load an asset through the content pipeline, using a localized variant of the <paramref name="assetName"/> if available.</summary>
/// <typeparam name="T">The type of asset to load.</typeparam>
@@ -65,10 +65,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
bool IsLoaded(IAssetName assetName);
+ /// <summary>Get all assets in the cache.</summary>
+ IEnumerable<KeyValuePair<string, object>> GetCachedAssets();
+
/// <summary>Purge matched assets from the cache.</summary>
- /// <param name="predicate">Matches the asset keys to invalidate.</param>
+ /// <param name="assetName">The asset name to dispose.</param>
/// <param name="dispose">Whether to dispose invalidated assets. This should only be <c>true</c> when they're being invalidated as part of a dispose, to avoid crashing the game.</param>
- /// <returns>Returns the invalidated asset names and instances.</returns>
- IDictionary<string, object> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false);
+ /// <returns>Returns whether the asset was in the cache.</returns>
+ bool InvalidateCache(IAssetName assetName, bool dispose = false);
}
}
diff --git a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs
index 20d206e2..7732ace8 100644
--- a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs
+++ b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs
@@ -48,4 +48,30 @@ namespace StardewModdingAPI.Framework.Utilities
return this.Cache.Remove(cacheKey);
}
}
+
+ /// <summary>An in-memory dictionary cache that stores data for the duration of a game update tick.</summary>
+ /// <typeparam name="TKey">The dictionary key type.</typeparam>
+ internal class TickCacheDictionary<TKey> : TickCacheDictionary<TKey, object>
+ where TKey : notnull
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Get a value from the cache, fetching it first if it's not cached yet.</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<TValue>(TKey cacheKey, Func<TValue> get)
+ {
+ object? value = base.GetOrSet(cacheKey, () => get()!);
+
+ try
+ {
+ return (TValue)value;
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidCastException($"Can't cast value of the '{cacheKey}' cache entry from {value?.GetType().FullName ?? "null"} to {typeof(TValue).FullName}.", ex);
+ }
+ }
+ }
}