From 7e8f4518764a86e7d3589ae75235b1d3d4462f8b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 31 Jan 2021 15:37:00 -0500 Subject: add experimental 'aggressive memory optimization' flag (#757) --- src/SMAPI/Metadata/CoreAssetPropagator.cs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'src/SMAPI/Metadata') diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 063804e0..829aa451 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -29,6 +29,9 @@ namespace StardewModdingAPI.Metadata /********* ** Fields *********/ + /// Whether to enable more aggressive memory optimizations. + private readonly bool AggressiveMemoryOptimizations; + /// Normalizes an asset key to match the cache key and assert that it's valid. private readonly Func AssertAndNormalizeAssetName; @@ -55,10 +58,12 @@ namespace StardewModdingAPI.Metadata /// Initialize the core asset data. /// Normalizes an asset key to match the cache key and assert that it's valid. /// Simplifies access to private code. - public CoreAssetPropagator(Func assertAndNormalizeAssetName, Reflector reflection) + /// Whether to enable more aggressive memory optimizations. + public CoreAssetPropagator(Func assertAndNormalizeAssetName, Reflector reflection, bool aggressiveMemoryOptimizations) { this.AssertAndNormalizeAssetName = assertAndNormalizeAssetName; this.Reflection = reflection; + this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; } /// Reload one of the game's core assets (if applicable). @@ -582,7 +587,7 @@ namespace StardewModdingAPI.Metadata titleMenu.aboutButton.texture = texture; titleMenu.languageButton.texture = texture; foreach (ClickableTextureComponent button in titleMenu.buttons) - button.texture = titleMenu.titleButtonsTexture; + button.texture = texture; foreach (TemporaryAnimatedSprite bird in titleMenu.birds) bird.texture = texture; @@ -785,6 +790,9 @@ namespace StardewModdingAPI.Metadata /// The location whose map to reload. private void ReloadMap(GameLocation location) { + if (this.AggressiveMemoryOptimizations) + location.map.DisposeTileSheets(Game1.mapDisplayDevice); + // reload map location.interiorDoors.Clear(); // prevent errors when doors try to update tiles which no longer exist location.reloadMap(); @@ -843,7 +851,7 @@ namespace StardewModdingAPI.Metadata // update sprite foreach (var target in characters) { - target.Npc.Sprite.spriteTexture = content.Load(target.Key); + target.Npc.Sprite.spriteTexture = this.DisposeIfNeeded(target.Npc.Sprite.spriteTexture, content.Load(target.Key)); propagated[target.Key] = true; } } @@ -881,7 +889,7 @@ namespace StardewModdingAPI.Metadata // update portrait foreach (var target in characters) { - target.Npc.Portrait = content.Load(target.Key); + target.Npc.Portrait = this.DisposeIfNeeded(target.Npc.Portrait, content.Load(target.Key)); propagated[target.Key] = true; } } @@ -1146,5 +1154,16 @@ namespace StardewModdingAPI.Metadata { return this.GetSegments(path).Length; } + + /// Dispose a texture if are enabled and it's different from the new instance. + /// The previous texture to dispose. + /// The new texture being loaded. + private Texture2D DisposeIfNeeded(Texture2D oldTexture, Texture2D newTexture) + { + if (this.AggressiveMemoryOptimizations && oldTexture != null && !oldTexture.IsDisposed && !object.ReferenceEquals(oldTexture, newTexture)) + oldTexture.Dispose(); + + return newTexture; + } } } -- cgit From 423f2352af9d0e9815daf4ba3ba33134f587ce47 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 31 Jan 2021 22:08:03 -0500 Subject: rework aggressive memory optimization to minimize mod impact (#757) --- src/SMAPI/Framework/ContentCoordinator.cs | 17 +++++- .../GameContentManagerForAssetPropagation.cs | 47 ++++++++++++++++ src/SMAPI/Metadata/CoreAssetPropagator.cs | 63 ++++++++++++++-------- 3 files changed, 103 insertions(+), 24 deletions(-) create mode 100644 src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs (limited to 'src/SMAPI/Metadata') diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 81265fa2..6a7385c3 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -117,8 +117,21 @@ namespace StardewModdingAPI.Framework aggressiveMemoryOptimizations: aggressiveMemoryOptimizations ) ); + var contentManagerForAssetPropagation = new GameContentManagerForAssetPropagation( + name: nameof(GameContentManagerForAssetPropagation), + serviceProvider: serviceProvider, + rootDirectory: rootDirectory, + currentCulture: currentCulture, + coordinator: this, + monitor: monitor, + reflection: reflection, + onDisposing: this.OnDisposing, + onLoadingFirstAsset: onLoadingFirstAsset, + aggressiveMemoryOptimizations: aggressiveMemoryOptimizations + ); + this.ContentManagers.Add(contentManagerForAssetPropagation); this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory); - this.CoreAssets = new CoreAssetPropagator(this.MainContentManager.AssertAndNormalizeAssetName, reflection, aggressiveMemoryOptimizations); + this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, contentManagerForAssetPropagation, reflection, aggressiveMemoryOptimizations); } /// Get a new content manager which handles reading files from the game content folder with support for interception. @@ -298,7 +311,7 @@ namespace StardewModdingAPI.Framework // reload core game assets if (removedAssets.Any()) { - IDictionary propagated = this.CoreAssets.Propagate(this.MainContentManager, removedAssets.ToDictionary(p => p.Key, p => p.Value)); // use an intercepted content manager + IDictionary propagated = this.CoreAssets.Propagate(removedAssets.ToDictionary(p => p.Key, p => p.Value)); // use an intercepted content manager this.Monitor.Log($"Invalidated {removedAssets.Count} asset names ({string.Join(", ", removedAssets.Keys.OrderBy(p => p, StringComparer.OrdinalIgnoreCase))}); propagated {propagated.Count(p => p.Value)} core assets.", LogLevel.Trace); } else diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs new file mode 100644 index 00000000..cbbebf02 --- /dev/null +++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs @@ -0,0 +1,47 @@ +using System; +using System.Globalization; +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.Reflection; +using StardewValley; + +namespace StardewModdingAPI.Framework.ContentManagers +{ + /// An extension of specifically optimized for asset propagation. + /// This avoids sharing an asset cache with or mods, so that assets can be safely disposed when the vanilla game no longer references them. + internal class GameContentManagerForAssetPropagation : GameContentManager + { + /********* + ** Fields + *********/ + /// A unique value used in to identify assets loaded through this instance. + private readonly string Tag = $"Pathoschild.SMAPI/LoadedBy:{nameof(GameContentManagerForAssetPropagation)}"; + + + /********* + ** Public methods + *********/ + /// + public GameContentManagerForAssetPropagation(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, Action onLoadingFirstAsset, bool aggressiveMemoryOptimizations) + : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, onLoadingFirstAsset, aggressiveMemoryOptimizations) { } + + /// + public override T Load(string assetName, LanguageCode language, bool useCache) + { + T data = base.Load(assetName, language, useCache); + + if (data is Texture2D texture) + texture.Tag = this.Tag; + + return data; + } + + /// Get whether a texture was loaded by this content manager. + /// The texture to check. + public bool IsReponsibleFor(Texture2D texture) + { + return + texture?.Tag is string tag + && tag.Contains(this.Tag); + } + } +} diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 829aa451..426fc3f6 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using Microsoft.Xna.Framework.Graphics; using Netcode; +using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; @@ -29,6 +30,12 @@ namespace StardewModdingAPI.Metadata /********* ** Fields *********/ + /// The main content manager through which to reload assets. + private readonly LocalizedContentManager MainContentManager; + + /// An internal content manager used only for asset propagation. See remarks on . + private readonly GameContentManagerForAssetPropagation DisposableContentManager; + /// Whether to enable more aggressive memory optimizations. private readonly bool AggressiveMemoryOptimizations; @@ -56,21 +63,24 @@ namespace StardewModdingAPI.Metadata ** Public methods *********/ /// Initialize the core asset data. - /// Normalizes an asset key to match the cache key and assert that it's valid. + /// The main content manager through which to reload assets. + /// An internal content manager used only for asset propagation. /// Simplifies access to private code. /// Whether to enable more aggressive memory optimizations. - public CoreAssetPropagator(Func assertAndNormalizeAssetName, Reflector reflection, bool aggressiveMemoryOptimizations) + public CoreAssetPropagator(LocalizedContentManager mainContent, GameContentManagerForAssetPropagation disposableContent, Reflector reflection, bool aggressiveMemoryOptimizations) { - this.AssertAndNormalizeAssetName = assertAndNormalizeAssetName; + this.MainContentManager = mainContent; + this.DisposableContentManager = disposableContent; this.Reflection = reflection; this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; + + this.AssertAndNormalizeAssetName = disposableContent.AssertAndNormalizeAssetName; } /// 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 a lookup of asset names to whether they've been propagated. - public IDictionary Propagate(LocalizedContentManager content, IDictionary assets) + public IDictionary Propagate(IDictionary assets) { // group into optimized lists var buckets = assets.GroupBy(p => @@ -91,16 +101,16 @@ namespace StardewModdingAPI.Metadata switch (bucket.Key) { case AssetBucket.Sprite: - this.ReloadNpcSprites(content, bucket.Select(p => p.Key), propagated); + this.ReloadNpcSprites(bucket.Select(p => p.Key), propagated); break; case AssetBucket.Portrait: - this.ReloadNpcPortraits(content, bucket.Select(p => p.Key), propagated); + this.ReloadNpcPortraits(bucket.Select(p => p.Key), propagated); break; default: foreach (var entry in bucket) - propagated[entry.Key] = this.PropagateOther(content, entry.Key, entry.Value); + propagated[entry.Key] = this.PropagateOther(entry.Key, entry.Value); break; } } @@ -112,13 +122,13 @@ namespace StardewModdingAPI.Metadata ** Private methods *********/ /// Reload one of the game's core assets (if applicable). - /// The content manager through which to reload the asset. /// The asset key to reload. /// The asset type to reload. /// Returns whether an asset was loaded. The return value may be true or false, or a non-null value for true. [SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These deliberately match the asset names.")] - private bool PropagateOther(LocalizedContentManager content, string key, Type type) + private bool PropagateOther(string key, Type type) { + var content = this.MainContentManager; key = this.AssertAndNormalizeAssetName(key); /**** @@ -830,10 +840,9 @@ namespace StardewModdingAPI.Metadata } /// Reload the sprites for matching NPCs. - /// The content manager through which to reload the asset. /// The asset keys to reload. /// The asset keys which have been propagated. - private void ReloadNpcSprites(LocalizedContentManager content, IEnumerable keys, IDictionary propagated) + private void ReloadNpcSprites(IEnumerable keys, IDictionary propagated) { // get NPCs HashSet lookup = new HashSet(keys, StringComparer.OrdinalIgnoreCase); @@ -851,16 +860,15 @@ namespace StardewModdingAPI.Metadata // update sprite foreach (var target in characters) { - target.Npc.Sprite.spriteTexture = this.DisposeIfNeeded(target.Npc.Sprite.spriteTexture, content.Load(target.Key)); + target.Npc.Sprite.spriteTexture = this.LoadAndDisposeIfNeeded(target.Npc.Sprite.spriteTexture, target.Key); propagated[target.Key] = true; } } /// Reload the portraits for matching NPCs. - /// The content manager through which to reload the asset. /// The asset key to reload. /// The asset keys which have been propagated. - private void ReloadNpcPortraits(LocalizedContentManager content, IEnumerable keys, IDictionary propagated) + private void ReloadNpcPortraits(IEnumerable keys, IDictionary propagated) { // get NPCs HashSet lookup = new HashSet(keys, StringComparer.OrdinalIgnoreCase); @@ -889,7 +897,7 @@ namespace StardewModdingAPI.Metadata // update portrait foreach (var target in characters) { - target.Npc.Portrait = this.DisposeIfNeeded(target.Npc.Portrait, content.Load(target.Key)); + target.Npc.Portrait = this.LoadAndDisposeIfNeeded(target.Npc.Portrait, target.Key); propagated[target.Key] = true; } } @@ -1155,15 +1163,26 @@ namespace StardewModdingAPI.Metadata return this.GetSegments(path).Length; } - /// Dispose a texture if are enabled and it's different from the new instance. + /// Load a texture, and dispose the old one if is enabled and it's different from the new instance. /// The previous texture to dispose. - /// The new texture being loaded. - private Texture2D DisposeIfNeeded(Texture2D oldTexture, Texture2D newTexture) + /// The asset key to load. + private Texture2D LoadAndDisposeIfNeeded(Texture2D oldTexture, string key) { - if (this.AggressiveMemoryOptimizations && oldTexture != null && !oldTexture.IsDisposed && !object.ReferenceEquals(oldTexture, newTexture)) - oldTexture.Dispose(); + // if aggressive memory optimizations are enabled, load the asset from the disposable + // content manager and dispose the old instance if needed. + if (this.AggressiveMemoryOptimizations) + { + GameContentManagerForAssetPropagation content = this.DisposableContentManager; + + Texture2D newTexture = content.Load(key); + if (oldTexture?.IsDisposed == false && !object.ReferenceEquals(oldTexture, newTexture) && content.IsReponsibleFor(oldTexture)) + oldTexture.Dispose(); + + return newTexture; + } - return newTexture; + // else just (re)load it from the main content manager + return this.MainContentManager.Load(key); } } } -- cgit From e81e07594ca4863f9feb94c882b59ba7cc00a0ae Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 31 Jan 2021 22:12:36 -0500 Subject: extend aggressive memory optimization to a few more common textures (#757) --- src/SMAPI/Metadata/CoreAssetPropagator.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'src/SMAPI/Metadata') diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 426fc3f6..e2a28c62 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -178,14 +178,19 @@ namespace StardewModdingAPI.Metadata ** Buildings ****/ case "buildings\\houses": // Farm - reflection.GetField(typeof(Farm), nameof(Farm.houseTextures)).SetValue(content.Load(key)); - return true; + { + var field = reflection.GetField(typeof(Farm), nameof(Farm.houseTextures)); + field.SetValue( + this.LoadAndDisposeIfNeeded(field.GetValue(), key) + ); + return true; + } /**** ** Content\Characters\Farmer ****/ case "characters\\farmer\\accessories": // Game1.LoadContent - FarmerRenderer.accessoriesTexture = content.Load(key); + FarmerRenderer.accessoriesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.accessoriesTexture, key); return true; case "characters\\farmer\\farmer_base": // Farmer @@ -195,19 +200,19 @@ namespace StardewModdingAPI.Metadata return this.ReloadPlayerSprites(key); case "characters\\farmer\\hairstyles": // Game1.LoadContent - FarmerRenderer.hairStylesTexture = content.Load(key); + FarmerRenderer.hairStylesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hairStylesTexture, key); return true; case "characters\\farmer\\hats": // Game1.LoadContent - FarmerRenderer.hatsTexture = content.Load(key); + FarmerRenderer.hatsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hatsTexture, key); return true; case "characters\\farmer\\pants": // Game1.LoadContent - FarmerRenderer.pantsTexture = content.Load(key); + FarmerRenderer.pantsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.pantsTexture, key); return true; case "characters\\farmer\\shirts": // Game1.LoadContent - FarmerRenderer.shirtsTexture = content.Load(key); + FarmerRenderer.shirtsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.shirtsTexture, key); return true; /**** -- cgit From b1876dec7ac6132b03b06277ef4d3bd8900b8805 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 13 Feb 2021 16:54:57 -0500 Subject: fix asset propagation for map seats --- docs/release-notes.md | 3 ++- src/SMAPI/Metadata/CoreAssetPropagator.cs | 25 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) (limited to 'src/SMAPI/Metadata') diff --git a/docs/release-notes.md b/docs/release-notes.md index e5cac9e0..82913873 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,8 +13,9 @@ * Fixed error running `install on Windows.bat` in very rare cases. * For modders: - * Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. + * Fixed asset propagation for `TileSheets/ChairTiles` not changing existing map seats. * Fixed edge case when playing in non-English where translatable assets loaded via `IAssetLoader` would no longer be applied after returning to the title screen unless manually invalidated from the cache. + * Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. * For the ErrorHandler mod: * Added early detection of disposed textures so the crash stack trace shows the actual code which used them. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index e2a28c62..5fb1b10d 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -452,8 +452,7 @@ namespace StardewModdingAPI.Metadata return true; case "tilesheets\\chairtiles": // Game1.LoadContent - MapSeat.mapChairTexture = content.Load(key); - return true; + return this.ReloadChairTiles(content, key); case "tilesheets\\craftables": // Game1.LoadContent Game1.bigCraftableSpriteSheet = content.Load(key); @@ -691,6 +690,28 @@ namespace StardewModdingAPI.Metadata return false; } + /// Reload map seat textures. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether any textures were reloaded. + private bool ReloadChairTiles(LocalizedContentManager content, string key) + { + MapSeat.mapChairTexture = content.Load(key); + + foreach (var location in this.GetLocations()) + { + foreach (MapSeat seat in location.mapSeats.Where(p => p != null)) + { + string curKey = this.NormalizeAssetNameIgnoringEmpty(seat._loadedTextureFile); + + if (curKey == null || key.Equals(curKey, StringComparison.OrdinalIgnoreCase)) + seat.overlayTexture = MapSeat.mapChairTexture; + } + } + + return true; + } + /// Reload critter textures. /// The content manager through which to reload the asset. /// The asset key to reload. -- cgit From ad80f8b078176519d49732b28a03465e6d8a6501 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 21 Feb 2021 13:50:08 -0500 Subject: use inheritdoc, minor cleanup --- .../ContentManagers/BaseContentManager.cs | 56 +++++++--------------- .../ContentManagers/GameContentManager.cs | 27 ++++------- .../GameContentManagerForAssetPropagation.cs | 2 +- .../Framework/ContentManagers/ModContentManager.cs | 21 ++------ src/SMAPI/Metadata/CoreAssetPropagator.cs | 2 +- 5 files changed, 32 insertions(+), 76 deletions(-) (limited to 'src/SMAPI/Metadata') diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index b6b3a7a0..1a64dab8 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -5,7 +5,6 @@ using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Linq; -using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Exceptions; @@ -53,16 +52,16 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Accessors *********/ - /// A name for the mod manager. Not guaranteed to be unique. + /// public string Name { get; } - /// The current language as a constant. + /// public LanguageCode Language => this.GetCurrentLanguage(); - /// The absolute path to the . + /// public string FullRootDirectory => Path.Combine(Constants.ExecutionPath, this.RootDirectory); - /// Whether this content manager can be targeted by managed asset keys (e.g. to load assets from a mod folder). + /// public bool IsNamespaced { get; } @@ -97,56 +96,42 @@ namespace StardewModdingAPI.Framework.ContentManagers this.BaseDisposableReferences = reflection.GetField>(this, "disposableAssets").GetValue(); } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. + /// public override T Load(string assetName) { return this.Load(assetName, this.Language, useCache: true); } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language code for which to load content. + /// public override T Load(string assetName, LanguageCode language) { return this.Load(assetName, language, useCache: true); } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language code for which to load content. - /// Whether to read/write the loaded asset to the asset cache. + /// public abstract T Load(string assetName, LocalizedContentManager.LanguageCode language, bool useCache); - /// Load the base asset without localization. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. + /// [Obsolete("This method is implemented for the base game and should not be used directly. To load an asset from the underlying content manager directly, use " + nameof(BaseContentManager.RawLoad) + " instead.")] public override T LoadBase(string assetName) { return this.Load(assetName, LanguageCode.en, useCache: true); } - /// Perform any cleanup needed when the locale changes. + /// public virtual void OnLocaleChanged() { } /// public virtual void OnReturningToTitleScreen() { } - /// Normalize path separators in a file path. For asset keys, see instead. - /// The file path to normalize. + /// [Pure] public string NormalizePathSeparators(string path) { return this.Cache.NormalizePathSeparators(path); } - /// Assert that the given key has a valid format and return a normalized form consistent with the underlying cache. - /// The asset key to check. - /// The asset key is empty or contains invalid characters. + /// [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local", Justification = "Parameter is only used for assertion checks by design.")] public string AssertAndNormalizeAssetName(string assetName) { @@ -163,29 +148,26 @@ namespace StardewModdingAPI.Framework.ContentManagers /**** ** Content loading ****/ - /// Get the current content locale. + /// public string GetLocale() { return this.GetLocale(this.GetCurrentLanguage()); } - /// The locale for a language. - /// The language. + /// public string GetLocale(LanguageCode language) { return this.LanguageCodeString(language); } - /// Get whether the content manager has already loaded and cached the given asset. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language. + /// public bool IsLoaded(string assetName, LanguageCode language) { assetName = this.Cache.NormalizeKey(assetName); return this.IsNormalizedKeyLoaded(assetName, language); } - /// Get the cached asset keys. + /// public IEnumerable GetAssetKeys() { return this.Cache.Keys @@ -196,10 +178,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /**** ** Cache invalidation ****/ - /// 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 instances. + /// public IDictionary InvalidateCache(Func predicate, bool dispose = false) { IDictionary removeAssets = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -228,8 +207,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return removeAssets; } - /// Dispose held resources. - /// Whether the content manager is being disposed (rather than finalized). + /// protected override void Dispose(bool isDisposing) { // ignore if disposed diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index e3d1e569..8e78faba 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -59,11 +59,7 @@ namespace StardewModdingAPI.Framework.ContentManagers this.OnLoadingFirstAsset = onLoadingFirstAsset; } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language code for which to load content. - /// Whether to read/write the loaded asset to the asset cache. + /// public override T Load(string assetName, LocalizedContentManager.LanguageCode language, bool useCache) { // raise first-load callback @@ -95,7 +91,7 @@ namespace StardewModdingAPI.Framework.ContentManagers if (this.AssetsBeingLoaded.Contains(assetName)) { this.Monitor.Log($"Broke loop while loading asset '{assetName}'.", LogLevel.Warn); - this.Monitor.Log($"Bypassing mod loaders for this asset. Stack trace:\n{Environment.StackTrace}", LogLevel.Trace); + this.Monitor.Log($"Bypassing mod loaders for this asset. Stack trace:\n{Environment.StackTrace}"); data = this.RawLoad(assetName, language, useCache); } else @@ -117,7 +113,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return data; } - /// Perform any cleanup needed when the locale changes. + /// public override void OnLocaleChanged() { base.OnLocaleChanged(); @@ -137,7 +133,7 @@ namespace StardewModdingAPI.Framework.ContentManagers .OrderBy(p => p, StringComparer.OrdinalIgnoreCase) .ToArray(); if (invalidated.Any()) - this.Monitor.Log($"Invalidated {invalidated.Length} asset names: {string.Join(", ", invalidated)} for locale change.", LogLevel.Trace); + this.Monitor.Log($"Invalidated {invalidated.Length} asset names: {string.Join(", ", invalidated)} for locale change."); } /// @@ -165,7 +161,7 @@ namespace StardewModdingAPI.Framework.ContentManagers this.InvalidateCache((_, _) => true); } - /// Create a new content manager for temporary use. + /// public override LocalizedContentManager CreateTemporary() { return this.Coordinator.CreateGameContentManager("(temporary)"); @@ -175,9 +171,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Private methods *********/ - /// Get whether an asset has already been loaded. - /// The normalized asset name. - /// The language to check. + /// protected override bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language) { string cachedKey = null; @@ -191,12 +185,7 @@ namespace StardewModdingAPI.Framework.ContentManagers : this.Cache.ContainsKey(normalizedAssetName); } - /// Add tracking data to an asset and add it to the cache. - /// The type of asset to inject. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The asset value. - /// The language code for which to inject the asset. - /// Whether to save the asset to the asset cache. + /// protected override void TrackAsset(string assetName, T value, LanguageCode language, bool useCache) { // handle explicit language in asset name @@ -384,7 +373,7 @@ namespace StardewModdingAPI.Framework.ContentManagers try { editor.Edit(asset); - this.Monitor.Log($"{mod.DisplayName} edited {info.AssetName}.", LogLevel.Trace); + this.Monitor.Log($"{mod.DisplayName} edited {info.AssetName}."); } catch (Exception ex) { diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs index cbbebf02..61683ce6 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs @@ -37,7 +37,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get whether a texture was loaded by this content manager. /// The texture to check. - public bool IsReponsibleFor(Texture2D texture) + public bool IsResponsibleFor(Texture2D texture) { return texture?.Tag is string tag diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 284c1f37..9af14cb5 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -59,28 +59,19 @@ namespace StardewModdingAPI.Framework.ContentManagers this.ModName = modName; } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. + /// public override T Load(string assetName) { return this.Load(assetName, this.DefaultLanguage, useCache: false); } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language code for which to load content. + /// public override T Load(string assetName, LanguageCode language) { return this.Load(assetName, language, useCache: false); } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language code for which to load content. - /// Whether to read/write the loaded asset to the asset cache. + /// public override T Load(string assetName, LanguageCode language, bool useCache) { assetName = this.AssertAndNormalizeAssetName(assetName); @@ -190,7 +181,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return asset; } - /// Create a new content manager for temporary use. + /// public override LocalizedContentManager CreateTemporary() { throw new NotSupportedException("Can't create a temporary mod content manager."); @@ -210,9 +201,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Private methods *********/ - /// Get whether an asset has already been loaded. - /// The normalized asset name. - /// The language to check. + /// protected override bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language) { return this.Cache.ContainsKey(normalizedAssetName); diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 5fb1b10d..8b591bc1 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -1201,7 +1201,7 @@ namespace StardewModdingAPI.Metadata GameContentManagerForAssetPropagation content = this.DisposableContentManager; Texture2D newTexture = content.Load(key); - if (oldTexture?.IsDisposed == false && !object.ReferenceEquals(oldTexture, newTexture) && content.IsReponsibleFor(oldTexture)) + if (oldTexture?.IsDisposed == false && !object.ReferenceEquals(oldTexture, newTexture) && content.IsResponsibleFor(oldTexture)) oldTexture.Dispose(); return newTexture; -- cgit