From e4a7ca5826ae0cb372aec529c4f21a53c98079da Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 12 Dec 2019 23:22:19 -0500 Subject: batch asset editor/loader changes --- .../Framework/Content/AssetInterceptorChange.cs | 91 ++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/SMAPI/Framework/Content/AssetInterceptorChange.cs (limited to 'src/SMAPI/Framework/Content') diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs new file mode 100644 index 00000000..498afe36 --- /dev/null +++ b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs @@ -0,0 +1,91 @@ +using System; +using System.Reflection; + +namespace StardewModdingAPI.Framework.Content +{ + /// A wrapper for and for internal cache invalidation. + internal class AssetInterceptorChange + { + /********* + ** Accessors + *********/ + /// The mod which registered the interceptor. + public IModMetadata Mod { get; } + + /// The interceptor instance. + public object Instance { get; } + + /// Whether the asset interceptor was added since the last tick. Mutually exclusive with . + public bool WasAdded { get; } + + /// Whether the asset interceptor was removed since the last tick. Mutually exclusive with . + public bool WasRemoved => this.WasAdded; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The mod registering the interceptor. + /// The interceptor. This must be an or instance. + /// Whether the asset interceptor was added since the last tick; else removed. + public AssetInterceptorChange(IModMetadata mod, object instance, bool wasAdded) + { + this.Mod = mod ?? throw new ArgumentNullException(nameof(mod)); + this.Instance = instance ?? throw new ArgumentNullException(nameof(instance)); + this.WasAdded = wasAdded; + + if (!(instance is IAssetEditor) && !(instance is IAssetLoader)) + throw new InvalidCastException($"The provided {nameof(instance)} value must be an {nameof(IAssetEditor)} or {nameof(IAssetLoader)} instance."); + } + + /// Get whether this instance can intercept the given asset. + /// Basic metadata about the asset being loaded. + public bool CanIntercept(IAssetInfo asset) + { + MethodInfo canIntercept = this.GetType().GetMethod(nameof(this.CanInterceptImpl), BindingFlags.Instance | BindingFlags.NonPublic); + if (canIntercept == null) + throw new InvalidOperationException($"SMAPI couldn't access the {nameof(AssetInterceptorChange)}.{nameof(this.CanInterceptImpl)} implementation."); + + return (bool)canIntercept.MakeGenericMethod(asset.DataType).Invoke(this, new object[] { asset }); + } + + + /********* + ** Private methods + *********/ + /// Get whether this instance can intercept the given asset. + /// The asset type. + /// Basic metadata about the asset being loaded. + private bool CanInterceptImpl(IAssetInfo asset) + { + // check edit + if (this.Instance is IAssetEditor editor) + { + try + { + return editor.CanEdit(asset); + } + catch (Exception ex) + { + this.Mod.LogAsMod($"Mod failed when checking whether it could edit asset '{asset.AssetName}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + } + } + + // check load + if (this.Instance is IAssetLoader loader) + { + try + { + return loader.CanLoad(asset); + } + catch (Exception ex) + { + this.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{asset.AssetName}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + } + } + + return false; + } + } +} -- cgit From ff94a8149ed5a0f597500bfb2b1896bdb2f1fff3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 12 Dec 2019 23:46:32 -0500 Subject: fix assets not being disposed when a content manager is disposed --- src/SMAPI/Framework/Content/ContentCache.cs | 2 +- src/SMAPI/Framework/ContentManagers/BaseContentManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/SMAPI/Framework/Content') diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index 4178b663..c252b7b6 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -119,7 +119,7 @@ namespace StardewModdingAPI.Framework.Content /// Matches the asset keys to invalidate. /// Whether to dispose invalidated assets. This should only be true when they're being invalidated as part of a dispose, to avoid crashing the game. /// Returns the removed keys (if any). - public IEnumerable Remove(Func predicate, bool dispose = false) + public IEnumerable Remove(Func predicate, bool dispose) { List removed = new List(); foreach (string key in this.Cache.Keys.ToArray()) diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 5283340e..93fd729b 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -200,7 +200,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return true; } return false; - }); + }, dispose); return removeAssetNames.Select(p => Tuple.Create(p.Key, p.Value)); } -- cgit From 16f986c51b9c87c2253a39fd771dcc24f7c43db4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 14 Dec 2019 21:31:34 -0500 Subject: refactor cache invalidation & propagation to allow for future optimizations --- src/SMAPI/Framework/Content/ContentCache.cs | 5 +- src/SMAPI/Framework/ContentCoordinator.cs | 25 ++++---- .../ContentManagers/BaseContentManager.cs | 16 +++--- .../ContentManagers/GameContentManager.cs | 2 +- .../Framework/ContentManagers/IContentManager.cs | 4 +- src/SMAPI/Metadata/CoreAssetPropagator.cs | 67 ++++++++++++---------- 6 files changed, 65 insertions(+), 54 deletions(-) (limited to 'src/SMAPI/Framework/Content') 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 /// Matches the asset keys to invalidate. /// Whether to dispose invalidated assets. This should only be true when they're being invalidated as part of a dispose, to avoid crashing the game. /// Returns the removed keys (if any). - public IEnumerable Remove(Func predicate, bool dispose) + public IEnumerable Remove(Func predicate, bool dispose) { List removed = new List(); 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 the invalidated asset names. public IEnumerable InvalidateCache(Func predicate, bool dispose = false) { - // invalidate cache - IDictionary removedAssetNames = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + // invalidate cache & track removed assets + IDictionary> removedAssets = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); foreach (IContentManager contentManager in this.ContentManagers) { - foreach (Tuple 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 assets)) + removedAssets[entry.Key] = assets = new HashSet(new ObjectReferenceComparer()); + 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 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; } /// Dispose held resources. 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 /// Purge matched assets from the cache. /// Matches the asset keys to invalidate. /// Whether to dispose invalidated assets. This should only be true when they're being invalidated as part of a dispose, to avoid crashing the game. - /// Returns the invalidated asset names and types. - public IEnumerable> InvalidateCache(Func predicate, bool dispose = false) + /// Returns the invalidated asset names and instances. + public IDictionary InvalidateCache(Func predicate, bool dispose = false) { - Dictionary removeAssetNames = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - this.Cache.Remove((key, type) => + IDictionary removeAssets = new Dictionary(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; } /// Dispose held resources. 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 /// Purge matched assets from the cache. /// Matches the asset keys to invalidate. /// Whether to dispose invalidated assets. This should only be true when they're being invalidated as part of a dispose, to avoid crashing the game. - /// Returns the invalidated asset names and types. - IEnumerable> InvalidateCache(Func predicate, bool dispose = false); + /// Returns the invalidated asset names and instances. + IDictionary InvalidateCache(Func 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 /// Reload one of the game's core assets (if applicable). /// The content manager through which to reload the asset. /// The asset keys and types to reload. - /// Returns the number of reloaded assets. - public int Propagate(LocalizedContentManager content, IDictionary assets) + /// Returns a lookup of asset names to whether they've been propagated. + public IDictionary Propagate(LocalizedContentManager content, IDictionary assets) { // group into optimized lists var buckets = assets.GroupBy(p => @@ -81,25 +81,26 @@ namespace StardewModdingAPI.Metadata }); // reload assets - int reloaded = 0; + IDictionary 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 /// Reload the sprites for matching NPCs. /// The content manager through which to reload the asset. /// The asset keys to reload. - /// Returns the number of reloaded assets. - private int ReloadNpcSprites(LocalizedContentManager content, IEnumerable keys) + /// The asset keys which have been propagated. + private void ReloadNpcSprites(LocalizedContentManager content, IEnumerable keys, IDictionary propagated) { // get NPCs HashSet lookup = new HashSet(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(npc.Sprite.textureName.Value)); - reloaded++; + this.SetSpriteTexture(target.Npc.Sprite, content.Load(target.Key)); + propagated[target.Key] = true; } - - return reloaded; } /// Reload the portraits for matching NPCs. /// The content manager through which to reload the asset. /// The asset key to reload. - /// Returns the number of reloaded assets. - private int ReloadNpcPortraits(LocalizedContentManager content, IEnumerable keys) + /// The asset keys which have been propagated. + private void ReloadNpcPortraits(LocalizedContentManager content, IEnumerable keys, IDictionary propagated) { // get NPCs HashSet lookup = new HashSet(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(npc.Portrait.Name); - reloaded++; + target.Npc.Portrait = content.Load(target.Key); + propagated[target.Key] = true; } - return reloaded; } /// Reload tree textures. -- cgit From 0a00c70397d85777499dcf7877cef08727a2dbae Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 20 Dec 2019 20:27:21 -0500 Subject: add console warning in paranoid mode --- docs/release-notes.md | 1 + src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs | 7 +++++-- src/SMAPI/Framework/Content/AssetDataForImage.cs | 4 ++-- src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 5 +++++ src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs | 3 +++ src/SMAPI/Framework/SCore.cs | 4 ++++ src/SMAPI/Metadata/InstructionMetadata.cs | 1 + 7 files changed, 21 insertions(+), 4 deletions(-) (limited to 'src/SMAPI/Framework/Content') diff --git a/docs/release-notes.md b/docs/release-notes.md index 772d95aa..761012df 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -20,6 +20,7 @@ * For modders: * Added asset propagation for grass textures. * Added asset propagation for `Data\Bundles` changes (for added bundles only). + * Added direct `Console` access to paranoid mode warnings. * Improved error messages for `TargetParameterCountException` when using the reflection API. * `helper.Read/WriteSaveData` can now be used while a save is being loaded (e.g. within a `Specialized.LoadStageChanged` event). * Fixed private textures loaded from content packs not having their `Name` field set. diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs index e67616d0..925e0b5c 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs @@ -27,10 +27,13 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData /// The mod has no update keys set. NoUpdateKeys = 32, + /// Uses .NET APIs for reading and writing to the console. + AccessesConsole = 64, + /// Uses .NET APIs for filesystem access. - AccessesFilesystem = 64, + AccessesFilesystem = 128, /// Uses .NET APIs for shell or process access. - AccessesShell = 128 + AccessesShell = 256 } } diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 4ae2ad68..aa615a0b 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -42,8 +42,8 @@ namespace StardewModdingAPI.Framework.Content Texture2D target = this.Data; // get areas - sourceArea = sourceArea ?? new Rectangle(0, 0, source.Width, source.Height); - targetArea = targetArea ?? new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height)); + sourceArea ??= new Rectangle(0, 0, source.Width, source.Height); + targetArea ??= new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height)); // validate if (sourceArea.Value.X < 0 || sourceArea.Value.Y < 0 || sourceArea.Value.Right > source.Width || sourceArea.Value.Bottom > source.Height) diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index 7670eb3a..b5533335 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -356,6 +356,11 @@ namespace StardewModdingAPI.Framework.ModLoading mod.SetWarning(ModWarning.UsesDynamic); break; + case InstructionHandleResult.DetectedConsoleAccess: + this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected direct console access ({handler.NounPhrase}) in assembly {filename}."); + mod.SetWarning(ModWarning.AccessesConsole); + break; + case InstructionHandleResult.DetectedFilesystemAccess: this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected filesystem access ({handler.NounPhrase}) in assembly {filename}."); mod.SetWarning(ModWarning.AccessesFilesystem); diff --git a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs index d93b603d..a948213b 100644 --- a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs +++ b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs @@ -26,6 +26,9 @@ namespace StardewModdingAPI.Framework.ModLoading /// The instruction is compatible, but references or which may impact stability. DetectedUnvalidatedUpdateTick, + /// The instruction accesses the SMAPI console directly. + DetectedConsoleAccess, + /// The instruction accesses the filesystem directly. DetectedFilesystemAccess, diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 2c6c0e76..c9c9d14a 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1102,6 +1102,10 @@ namespace StardewModdingAPI.Framework ); if (this.Settings.ParanoidWarnings) { + LogWarningGroup(ModWarning.AccessesConsole, LogLevel.Warn, "Accesses the console directly", + "These mods directly access the SMAPI console, and you enabled paranoid warnings. (Note that this may be", + "legitimate and innocent usage; this warning is meaningless without further investigation.)" + ); LogWarningGroup(ModWarning.AccessesFilesystem, LogLevel.Warn, "Accesses filesystem directly", "These mods directly access the filesystem, and you enabled paranoid warnings. (Note that this may be", "legitimate and innocent usage; this warning is meaningless without further investigation.)" diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 95482708..eee5c235 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -60,6 +60,7 @@ namespace StardewModdingAPI.Metadata if (paranoidMode) { // filesystem access + yield return new TypeFinder(typeof(System.Console).FullName, InstructionHandleResult.DetectedConsoleAccess); yield return new TypeFinder(typeof(System.IO.File).FullName, InstructionHandleResult.DetectedFilesystemAccess); yield return new TypeFinder(typeof(System.IO.FileStream).FullName, InstructionHandleResult.DetectedFilesystemAccess); yield return new TypeFinder(typeof(System.IO.FileInfo).FullName, InstructionHandleResult.DetectedFilesystemAccess); -- cgit From 93459a5e37b26cc8d742878dd993543c43f70694 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 4 Jan 2020 22:08:01 -0500 Subject: fix new asset invalidation code not correctly handling interceptors which both load and edit --- .../Framework/Content/AssetInterceptorChange.cs | 6 ++++-- src/SMAPI/Framework/SGame.cs | 20 ++++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) (limited to 'src/SMAPI/Framework/Content') diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs index 498afe36..037d9f89 100644 --- a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs +++ b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs @@ -64,7 +64,8 @@ namespace StardewModdingAPI.Framework.Content { try { - return editor.CanEdit(asset); + if (editor.CanEdit(asset)) + return true; } catch (Exception ex) { @@ -77,7 +78,8 @@ namespace StardewModdingAPI.Framework.Content { try { - return loader.CanLoad(asset); + if (loader.CanLoad(asset)) + return true; } catch (Exception ex) { diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index e2b22ba7..d6c3b836 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -18,6 +18,7 @@ using StardewModdingAPI.Framework.Events; using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Framework.Networking; using StardewModdingAPI.Framework.Reflection; +using StardewModdingAPI.Framework.StateTracking.Comparers; using StardewModdingAPI.Framework.StateTracking.Snapshots; using StardewModdingAPI.Framework.Utilities; using StardewModdingAPI.Toolkit.Serialization; @@ -427,17 +428,24 @@ namespace StardewModdingAPI.Framework return; } - /********* ** Reload assets when interceptors are added/removed *********/ if (this.ReloadAssetInterceptorsQueue.Any()) { - this.Monitor.Log("Invalidating cached assets for new editors & loaders...", LogLevel.Trace); + // get unique interceptors + AssetInterceptorChange[] interceptors = this.ReloadAssetInterceptorsQueue + .GroupBy(p => p.Instance, new ObjectReferenceComparer()) + .Select(p => p.First()) + .ToArray(); + this.ReloadAssetInterceptorsQueue.Clear(); + + // log summary + this.Monitor.Log("Invalidating cached assets for new editors & loaders..."); this.Monitor.Log( - "changed: " + " changed: " + string.Join(", ", - this.ReloadAssetInterceptorsQueue + interceptors .GroupBy(p => p.Mod) .OrderBy(p => p.Key.DisplayName) .Select(modGroup => @@ -448,8 +456,8 @@ namespace StardewModdingAPI.Framework ) ); - this.ContentCore.InvalidateCache(asset => this.ReloadAssetInterceptorsQueue.Any(p => p.CanIntercept(asset))); - this.ReloadAssetInterceptorsQueue.Clear(); + // reload affected assets + this.ContentCore.InvalidateCache(asset => interceptors.Any(p => p.CanIntercept(asset))); } /********* -- cgit