summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/SMAPI/Framework/Content/ContentCache.cs5
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs25
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs16
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs2
-rw-r--r--src/SMAPI/Framework/ContentManagers/IContentManager.cs4
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs67
6 files changed, 65 insertions, 54 deletions
diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs
index c252b7b6..f33ff84d 100644
--- a/src/SMAPI/Framework/Content/ContentCache.cs
+++ b/src/SMAPI/Framework/Content/ContentCache.cs
@@ -119,13 +119,12 @@ namespace StardewModdingAPI.Framework.Content
/// <param name="predicate">Matches the asset keys to invalidate.</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 removed keys (if any).</returns>
- public IEnumerable<string> Remove(Func<string, Type, bool> predicate, bool dispose)
+ public IEnumerable<string> Remove(Func<string, object, bool> predicate, bool dispose)
{
List<string> removed = new List<string>();
foreach (string key in this.Cache.Keys.ToArray())
{
- Type type = this.Cache[key].GetType();
- if (predicate(key, type))
+ if (predicate(key, this.Cache[key]))
{
this.Remove(key, dispose);
removed.Add(key);
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 97b54c5b..82d3805b 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -7,6 +7,7 @@ using Microsoft.Xna.Framework.Content;
using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Reflection;
+using StardewModdingAPI.Framework.StateTracking.Comparers;
using StardewModdingAPI.Metadata;
using StardewModdingAPI.Toolkit.Serialization;
using StardewModdingAPI.Toolkit.Utilities;
@@ -207,24 +208,28 @@ namespace StardewModdingAPI.Framework
/// <returns>Returns the invalidated asset names.</returns>
public IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
{
- // invalidate cache
- IDictionary<string, Type> removedAssetNames = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
+ // invalidate cache & track removed assets
+ IDictionary<string, ISet<object>> removedAssets = new Dictionary<string, ISet<object>>(StringComparer.InvariantCultureIgnoreCase);
foreach (IContentManager contentManager in this.ContentManagers)
{
- foreach (Tuple<string, Type> asset in contentManager.InvalidateCache(predicate, dispose))
- removedAssetNames[asset.Item1] = asset.Item2;
+ foreach (var entry in contentManager.InvalidateCache(predicate, dispose))
+ {
+ if (!removedAssets.TryGetValue(entry.Key, out ISet<object> assets))
+ removedAssets[entry.Key] = assets = new HashSet<object>(new ObjectReferenceComparer<object>());
+ assets.Add(entry.Value);
+ }
}
// reload core game assets
- int reloaded = this.CoreAssets.Propagate(this.MainContentManager, removedAssetNames); // use an intercepted content manager
-
- // report result
- if (removedAssetNames.Any())
- this.Monitor.Log($"Invalidated {removedAssetNames.Count} asset names: {string.Join(", ", removedAssetNames.Keys.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}. Reloaded {reloaded} core assets.", LogLevel.Trace);
+ if (removedAssets.Any())
+ {
+ IDictionary<string, bool> propagated = this.CoreAssets.Propagate(this.MainContentManager, removedAssets.ToDictionary(p => p.Key, p => p.Value.First().GetType())); // use an intercepted content manager
+ this.Monitor.Log($"Invalidated {removedAssets.Count} asset names ({string.Join(", ", removedAssets.Keys.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}); propagated {propagated.Count(p => p.Value)} core assets.", LogLevel.Trace);
+ }
else
this.Monitor.Log("Invalidated 0 cache entries.", LogLevel.Trace);
- return removedAssetNames.Keys;
+ return removedAssets.Keys;
}
/// <summary>Dispose held resources.</summary>
diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
index 4cfeeeba..41ce7c37 100644
--- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
@@ -184,25 +184,25 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Purge matched assets from the cache.</summary>
/// <param name="predicate">Matches the asset keys to invalidate.</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 types.</returns>
- public IEnumerable<Tuple<string, Type>> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
+ /// <returns>Returns the invalidated asset names and instances.</returns>
+ public IDictionary<string, object> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
{
- Dictionary<string, Type> removeAssetNames = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
- this.Cache.Remove((key, type) =>
+ IDictionary<string, object> removeAssets = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
+ this.Cache.Remove((key, asset) =>
{
this.ParseCacheKey(key, out string assetName, out _);
- if (removeAssetNames.ContainsKey(assetName))
+ if (removeAssets.ContainsKey(assetName))
return true;
- if (predicate(assetName, type))
+ if (predicate(assetName, asset.GetType()))
{
- removeAssetNames[assetName] = type;
+ removeAssets[assetName] = asset;
return true;
}
return false;
}, dispose);
- return removeAssetNames.Select(p => Tuple.Create(p.Key, p.Value));
+ return removeAssets;
}
/// <summary>Dispose held resources.</summary>
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index 04c4564f..8930267d 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -130,7 +130,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
removeAssetNames.Contains(key)
|| (this.TryParseExplicitLanguageAssetKey(key, out string assetName, out _) && removeAssetNames.Contains(assetName))
)
- .Select(p => p.Item1)
+ .Select(p => p.Key)
.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase)
.ToArray();
if (invalidated.Any())
diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
index 12c01352..8da9a777 100644
--- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
@@ -66,7 +66,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Purge matched assets from the cache.</summary>
/// <param name="predicate">Matches the asset keys to invalidate.</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 types.</returns>
- IEnumerable<Tuple<string, Type>> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false);
+ /// <returns>Returns the invalidated asset names and instances.</returns>
+ IDictionary<string, object> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false);
}
}
diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs
index 8b00d893..84102828 100644
--- a/src/SMAPI/Metadata/CoreAssetPropagator.cs
+++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs
@@ -65,8 +65,8 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload one of the game's core assets (if applicable).</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
/// <param name="assets">The asset keys and types to reload.</param>
- /// <returns>Returns the number of reloaded assets.</returns>
- public int Propagate(LocalizedContentManager content, IDictionary<string, Type> assets)
+ /// <returns>Returns a lookup of asset names to whether they've been propagated.</returns>
+ public IDictionary<string, bool> Propagate(LocalizedContentManager content, IDictionary<string, Type> assets)
{
// group into optimized lists
var buckets = assets.GroupBy(p =>
@@ -81,25 +81,26 @@ namespace StardewModdingAPI.Metadata
});
// reload assets
- int reloaded = 0;
+ IDictionary<string, bool> propagated = assets.ToDictionary(p => p.Key, p => false, StringComparer.InvariantCultureIgnoreCase);
foreach (var bucket in buckets)
{
switch (bucket.Key)
{
case AssetBucket.Sprite:
- reloaded += this.ReloadNpcSprites(content, bucket.Select(p => p.Key));
+ this.ReloadNpcSprites(content, bucket.Select(p => p.Key), propagated);
break;
case AssetBucket.Portrait:
- reloaded += this.ReloadNpcPortraits(content, bucket.Select(p => p.Key));
+ this.ReloadNpcPortraits(content, bucket.Select(p => p.Key), propagated);
break;
default:
- reloaded += bucket.Count(p => this.PropagateOther(content, p.Key, p.Value));
+ foreach (var entry in bucket)
+ propagated[entry.Key] = this.PropagateOther(content, entry.Key, entry.Value);
break;
}
}
- return reloaded;
+ return propagated;
}
@@ -750,51 +751,57 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload the sprites for matching NPCs.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
/// <param name="keys">The asset keys to reload.</param>
- /// <returns>Returns the number of reloaded assets.</returns>
- private int ReloadNpcSprites(LocalizedContentManager content, IEnumerable<string> keys)
+ /// <param name="propagated">The asset keys which have been propagated.</param>
+ private void ReloadNpcSprites(LocalizedContentManager content, IEnumerable<string> keys, IDictionary<string, bool> propagated)
{
// get NPCs
HashSet<string> lookup = new HashSet<string>(keys, StringComparer.InvariantCultureIgnoreCase);
- NPC[] characters = this.GetCharacters()
- .Where(npc => npc.Sprite != null && lookup.Contains(this.NormalizeAssetNameIgnoringEmpty(npc.Sprite?.Texture?.Name)))
+ var characters =
+ (
+ from npc in this.GetCharacters()
+ let key = this.NormalizeAssetNameIgnoringEmpty(npc.Sprite?.Texture?.Name)
+ where key != null && lookup.Contains(key)
+ select new { Npc = npc, Key = key }
+ )
.ToArray();
if (!characters.Any())
- return 0;
+ return;
// update sprite
- int reloaded = 0;
- foreach (NPC npc in characters)
+ foreach (var target in characters)
{
- this.SetSpriteTexture(npc.Sprite, content.Load<Texture2D>(npc.Sprite.textureName.Value));
- reloaded++;
+ this.SetSpriteTexture(target.Npc.Sprite, content.Load<Texture2D>(target.Key));
+ propagated[target.Key] = true;
}
-
- return reloaded;
}
/// <summary>Reload the portraits for matching NPCs.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
/// <param name="keys">The asset key to reload.</param>
- /// <returns>Returns the number of reloaded assets.</returns>
- private int ReloadNpcPortraits(LocalizedContentManager content, IEnumerable<string> keys)
+ /// <param name="propagated">The asset keys which have been propagated.</param>
+ private void ReloadNpcPortraits(LocalizedContentManager content, IEnumerable<string> keys, IDictionary<string, bool> propagated)
{
// get NPCs
HashSet<string> lookup = new HashSet<string>(keys, StringComparer.InvariantCultureIgnoreCase);
- var villagers = this
- .GetCharacters()
- .Where(npc => npc.isVillager() && lookup.Contains(this.NormalizeAssetNameIgnoringEmpty(npc.Portrait?.Name)))
+ var characters =
+ (
+ from npc in this.GetCharacters()
+ where npc.isVillager()
+
+ let key = this.NormalizeAssetNameIgnoringEmpty(npc.Portrait?.Name)
+ where key != null && lookup.Contains(key)
+ select new { Npc = npc, Key = key }
+ )
.ToArray();
- if (!villagers.Any())
- return 0;
+ if (!characters.Any())
+ return;
// update portrait
- int reloaded = 0;
- foreach (NPC npc in villagers)
+ foreach (var target in characters)
{
- npc.Portrait = content.Load<Texture2D>(npc.Portrait.Name);
- reloaded++;
+ target.Npc.Portrait = content.Load<Texture2D>(target.Key);
+ propagated[target.Key] = true;
}
- return reloaded;
}
/// <summary>Reload tree textures.</summary>