add support for propagating map asset changes

This commit is contained in:
Jesse Plamondon-Willard 2018-11-25 00:07:26 -05:00
parent 43f11cfe51
commit fb253941df
No known key found for this signature in database
GPG Key ID: 7D7C8097B62033CE
5 changed files with 46 additions and 19 deletions

View File

@ -4,6 +4,7 @@
* Fixed cryptic error when running the installer from inside a zip file on Windows. * Fixed cryptic error when running the installer from inside a zip file on Windows.
* For modders: * For modders:
* Reloading a map asset will now automatically update affected locations.
* Fixed newlines in most manifest fields not being ignored. * Fixed newlines in most manifest fields not being ignored.
* For the web UI: * For the web UI:

View File

@ -238,28 +238,30 @@ namespace StardewModdingAPI.Framework
public IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false) public IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
{ {
// invalidate cache // invalidate cache
HashSet<string> removedAssetNames = new HashSet<string>(); IDictionary<string, Type> removedAssetNames = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
foreach (IContentManager contentManager in this.ContentManagers) foreach (IContentManager contentManager in this.ContentManagers)
{ {
foreach (string name in contentManager.InvalidateCache(predicate, dispose)) foreach (Tuple<string, Type> asset in contentManager.InvalidateCache(predicate, dispose))
removedAssetNames.Add(name); removedAssetNames[asset.Item1] = asset.Item2;
} }
// reload core game assets // reload core game assets
int reloaded = 0; int reloaded = 0;
foreach (string key in removedAssetNames) foreach (var pair in removedAssetNames)
{ {
if (this.CoreAssets.Propagate(this.MainContentManager, key)) // use an intercepted content manager string key = pair.Key;
Type type = pair.Value;
if (this.CoreAssets.Propagate(this.MainContentManager, key, type)) // use an intercepted content manager
reloaded++; reloaded++;
} }
// report result // report result
if (removedAssetNames.Any()) if (removedAssetNames.Any())
this.Monitor.Log($"Invalidated {removedAssetNames.Count} asset names: {string.Join(", ", removedAssetNames.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}. Reloaded {reloaded} core assets.", LogLevel.Trace); this.Monitor.Log($"Invalidated {removedAssetNames.Count} asset names: {string.Join(", ", removedAssetNames.Keys.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}. Reloaded {reloaded} core assets.", LogLevel.Trace);
else else
this.Monitor.Log("Invalidated 0 cache entries.", LogLevel.Trace); this.Monitor.Log("Invalidated 0 cache entries.", LogLevel.Trace);
return removedAssetNames; return removedAssetNames.Keys;
} }
/// <summary>Dispose held resources.</summary> /// <summary>Dispose held resources.</summary>

View File

@ -200,23 +200,25 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Purge matched assets from the cache.</summary> /// <summary>Purge matched assets from the cache.</summary>
/// <param name="predicate">Matches the asset keys to invalidate.</param> /// <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> /// <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 number of invalidated assets.</returns> /// <returns>Returns the invalidated asset names and types.</returns>
public IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false) public IEnumerable<Tuple<string, Type>> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
{ {
HashSet<string> removeAssetNames = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); Dictionary<string, Type> removeAssetNames = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
this.Cache.Remove((key, type) => this.Cache.Remove((key, type) =>
{ {
this.ParseCacheKey(key, out string assetName, out _); this.ParseCacheKey(key, out string assetName, out _);
if (removeAssetNames.Contains(assetName) || predicate(assetName, type)) if (removeAssetNames.ContainsKey(assetName))
return true;
if (predicate(assetName, type))
{ {
removeAssetNames.Add(assetName); removeAssetNames[assetName] = type;
return true; return true;
} }
return false; return false;
}); });
return removeAssetNames; return removeAssetNames.Select(p => Tuple.Create(p.Key, p.Value));
} }
/// <summary>Dispose held resources.</summary> /// <summary>Dispose held resources.</summary>

View File

@ -80,7 +80,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Purge matched assets from the cache.</summary> /// <summary>Purge matched assets from the cache.</summary>
/// <param name="predicate">Matches the asset keys to invalidate.</param> /// <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> /// <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 number of invalidated assets.</returns> /// <returns>Returns the invalidated asset names and types.</returns>
IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false); IEnumerable<Tuple<string, Type>> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false);
} }
} }

View File

@ -13,6 +13,7 @@ using StardewValley.Menus;
using StardewValley.Objects; using StardewValley.Objects;
using StardewValley.Projectiles; using StardewValley.Projectiles;
using StardewValley.TerrainFeatures; using StardewValley.TerrainFeatures;
using xTile;
using xTile.Tiles; using xTile.Tiles;
namespace StardewModdingAPI.Metadata namespace StardewModdingAPI.Metadata
@ -45,10 +46,11 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload one of the game's core assets (if applicable).</summary> /// <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="content">The content manager through which to reload the asset.</param>
/// <param name="key">The asset key to reload.</param> /// <param name="key">The asset key to reload.</param>
/// <param name="type">The asset type to reload.</param>
/// <returns>Returns whether an asset was reloaded.</returns> /// <returns>Returns whether an asset was reloaded.</returns>
public bool Propagate(LocalizedContentManager content, string key) public bool Propagate(LocalizedContentManager content, string key, Type type)
{ {
object result = this.PropagateImpl(content, key); object result = this.PropagateImpl(content, key, type);
if (result is bool b) if (result is bool b)
return b; return b;
return result != null; return result != null;
@ -61,9 +63,12 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload one of the game's core assets (if applicable).</summary> /// <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="content">The content manager through which to reload the asset.</param>
/// <param name="key">The asset key to reload.</param> /// <param name="key">The asset key to reload.</param>
/// <returns>Returns any non-null value to indicate an asset was loaded.</returns> /// <param name="type">The asset type to reload.</param>
private object PropagateImpl(LocalizedContentManager content, string key) /// <returns>Returns whether an asset was loaded. The return value may be true or false, or a non-null value for true.</returns>
private object PropagateImpl(LocalizedContentManager content, string key, Type type)
{ {
key = this.GetNormalisedPath(key);
/**** /****
** Special case: current map tilesheet ** Special case: current map tilesheet
** We only need to do this for the current location, since tilesheets are reloaded when you enter a location. ** We only need to do this for the current location, since tilesheets are reloaded when you enter a location.
@ -78,6 +83,23 @@ namespace StardewModdingAPI.Metadata
} }
} }
/****
** Propagate map changes
****/
if (type == typeof(Map))
{
bool anyChanged = false;
foreach (GameLocation location in this.GetLocations())
{
if (this.GetNormalisedPath(location.mapPath.Value) == key)
{
this.Reflection.GetMethod(location, "reloadMap").Invoke();
anyChanged = true;
}
}
return anyChanged;
}
/**** /****
** Propagate by key ** Propagate by key
****/ ****/