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/Framework/ContentCoordinator.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'src/SMAPI/Framework/ContentCoordinator.cs') diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 77dd6c72..81265fa2 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -26,6 +26,9 @@ namespace StardewModdingAPI.Framework /// An asset key prefix for assets from SMAPI mod folders. private readonly string ManagedPrefix = "SMAPI"; + /// Whether to enable more aggressive memory optimizations. + private readonly bool AggressiveMemoryOptimizations; + /// Encapsulates monitoring and logging. private readonly IMonitor Monitor; @@ -91,8 +94,10 @@ namespace StardewModdingAPI.Framework /// Simplifies access to private code. /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke the first time *any* game content manager loads an asset. - public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset) + /// Whether to enable more aggressive memory optimizations. + public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, bool aggressiveMemoryOptimizations) { + this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); this.Reflection = reflection; this.JsonHelper = jsonHelper; @@ -108,11 +113,12 @@ namespace StardewModdingAPI.Framework monitor: monitor, reflection: reflection, onDisposing: this.OnDisposing, - onLoadingFirstAsset: onLoadingFirstAsset + onLoadingFirstAsset: onLoadingFirstAsset, + aggressiveMemoryOptimizations: aggressiveMemoryOptimizations ) ); this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory); - this.CoreAssets = new CoreAssetPropagator(this.MainContentManager.AssertAndNormalizeAssetName, reflection); + this.CoreAssets = new CoreAssetPropagator(this.MainContentManager.AssertAndNormalizeAssetName, reflection, aggressiveMemoryOptimizations); } /// Get a new content manager which handles reading files from the game content folder with support for interception. @@ -130,7 +136,8 @@ namespace StardewModdingAPI.Framework monitor: this.Monitor, reflection: this.Reflection, onDisposing: this.OnDisposing, - onLoadingFirstAsset: this.OnLoadingFirstAsset + onLoadingFirstAsset: this.OnLoadingFirstAsset, + aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations ); this.ContentManagers.Add(manager); return manager; @@ -157,7 +164,8 @@ namespace StardewModdingAPI.Framework monitor: this.Monitor, reflection: this.Reflection, jsonHelper: this.JsonHelper, - onDisposing: this.OnDisposing + onDisposing: this.OnDisposing, + aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations ); this.ContentManagers.Add(manager); return manager; -- 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/Framework/ContentCoordinator.cs') 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 efec87065780426db15c51a7e68315ff488a89c0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 6 Feb 2021 13:03:37 -0500 Subject: fix edge case in non-English asset cache after returning to title screen --- docs/release-notes.md | 1 + src/SMAPI/Framework/ContentCoordinator.cs | 26 ++++++++++++++++++++++++++ src/SMAPI/Framework/SCore.cs | 1 + 3 files changed, 28 insertions(+) (limited to 'src/SMAPI/Framework/ContentCoordinator.cs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 9cea9fa9..9f4ea50a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -14,6 +14,7 @@ * 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 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. ## 3.9.1 Released 25 January 2021 for Stardew Valley 1.5.4 or later. diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 6a7385c3..b7c15526 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -203,6 +203,32 @@ namespace StardewModdingAPI.Framework }); } + /// Clean up when the player is returning to the title screen. + /// This is called after the player returns to the title screen, but before runs. + public void OnReturningToTitleScreen() + { + // The game clears LocalizedContentManager.localizedAssetNames after returning to the title screen. That + // causes an inconsistency in the SMAPI asset cache, which leads to an edge case where assets already + // provided by mods via IAssetLoader when playing in non-English are ignored. + // + // For example, let's say a mod provides the 'Data\mail' asset through IAssetLoader when playing in + // Portuguese. Here's the normal load process after it's loaded: + // 1. The game requests Data\mail. + // 2. SMAPI sees that it's already cached, and calls LoadRaw to bypass asset interception. + // 3. LoadRaw sees that there's a localized key mapping, and gets the mapped key. + // 4. In this case "Data\mail" is mapped to "Data\mail" since it was loaded by a mod, so it loads that + // asset. + // + // When the game clears localizedAssetNames, that process goes wrong in step 4: + // 3. LoadRaw sees that there's no localized key mapping *and* the locale is non-English, so it attempts + // to load from the localized key format. + // 4. In this case that's 'Data\mail.pt-BR', so it successfully loads that asset. + // 5. Since we've bypassed asset interception at this point, it's loaded directly from the base content + // manager without mod changes. + + this.InvalidateCache(asset => true); + } + /// Get whether this asset is mapped to a mod folder. /// The asset key. public bool IsManagedAssetKey(string key) diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 0ae69f0f..2d783eb2 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1118,6 +1118,7 @@ namespace StardewModdingAPI.Framework { // perform cleanup this.Multiplayer.CleanupOnMultiplayerExit(); + this.ContentCore.OnReturningToTitleScreen(); this.JustReturnedToTitle = true; } -- cgit From 79aee3fba7a43025d84527f883ce6715d7140302 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 21 Feb 2021 13:45:10 -0500 Subject: reduce performance impact of new cache fix --- src/SMAPI/Framework/ContentCoordinator.cs | 27 +++++----------------- .../ContentManagers/BaseContentManager.cs | 3 +++ .../ContentManagers/GameContentManager.cs | 25 ++++++++++++++++++++ .../Framework/ContentManagers/IContentManager.cs | 10 +++++--- 4 files changed, 41 insertions(+), 24 deletions(-) (limited to 'src/SMAPI/Framework/ContentCoordinator.cs') diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index b7c15526..32195fff 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -54,7 +54,7 @@ namespace StardewModdingAPI.Framework private bool IsDisposed; /// A lock used to prevent asynchronous changes to the content manager list. - /// The game may adds content managers in asynchronous threads (e.g. when populating the load screen). + /// The game may add content managers in asynchronous threads (e.g. when populating the load screen). private readonly ReaderWriterLockSlim ContentManagerLock = new ReaderWriterLockSlim(); /// A cache of ordered tilesheet IDs used by vanilla maps. @@ -207,26 +207,11 @@ namespace StardewModdingAPI.Framework /// This is called after the player returns to the title screen, but before runs. public void OnReturningToTitleScreen() { - // The game clears LocalizedContentManager.localizedAssetNames after returning to the title screen. That - // causes an inconsistency in the SMAPI asset cache, which leads to an edge case where assets already - // provided by mods via IAssetLoader when playing in non-English are ignored. - // - // For example, let's say a mod provides the 'Data\mail' asset through IAssetLoader when playing in - // Portuguese. Here's the normal load process after it's loaded: - // 1. The game requests Data\mail. - // 2. SMAPI sees that it's already cached, and calls LoadRaw to bypass asset interception. - // 3. LoadRaw sees that there's a localized key mapping, and gets the mapped key. - // 4. In this case "Data\mail" is mapped to "Data\mail" since it was loaded by a mod, so it loads that - // asset. - // - // When the game clears localizedAssetNames, that process goes wrong in step 4: - // 3. LoadRaw sees that there's no localized key mapping *and* the locale is non-English, so it attempts - // to load from the localized key format. - // 4. In this case that's 'Data\mail.pt-BR', so it successfully loads that asset. - // 5. Since we've bypassed asset interception at this point, it's loaded directly from the base content - // manager without mod changes. - - this.InvalidateCache(asset => true); + this.ContentManagerLock.InReadLock(() => + { + foreach (IContentManager contentManager in this.ContentManagers) + contentManager.OnReturningToTitleScreen(); + }); } /// Get whether this asset is mapped to a mod folder. diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 709c624e..b6b3a7a0 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -133,6 +133,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// 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] diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index f8ee575f..e3d1e569 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -140,6 +140,31 @@ namespace StardewModdingAPI.Framework.ContentManagers this.Monitor.Log($"Invalidated {invalidated.Length} asset names: {string.Join(", ", invalidated)} for locale change.", LogLevel.Trace); } + /// + public override void OnReturningToTitleScreen() + { + // The game clears LocalizedContentManager.localizedAssetNames after returning to the title screen. That + // causes an inconsistency in the SMAPI asset cache, which leads to an edge case where assets already + // provided by mods via IAssetLoader when playing in non-English are ignored. + // + // For example, let's say a mod provides the 'Data\mail' asset through IAssetLoader when playing in + // Portuguese. Here's the normal load process after it's loaded: + // 1. The game requests Data\mail. + // 2. SMAPI sees that it's already cached, and calls LoadRaw to bypass asset interception. + // 3. LoadRaw sees that there's a localized key mapping, and gets the mapped key. + // 4. In this case "Data\mail" is mapped to "Data\mail" since it was loaded by a mod, so it loads that + // asset. + // + // When the game clears localizedAssetNames, that process goes wrong in step 4: + // 3. LoadRaw sees that there's no localized key mapping *and* the locale is non-English, so it attempts + // to load from the localized key format. + // 4. In this case that's 'Data\mail.pt-BR', so it successfully loads that asset. + // 5. Since we've bypassed asset interception at this point, it's loaded directly from the base content + // manager without mod changes. + if (LocalizedContentManager.CurrentLanguageCode != LocalizedContentManager.LanguageCode.en) + this.InvalidateCache((_, _) => true); + } + /// Create a new content manager for temporary use. public override LocalizedContentManager CreateTemporary() { diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index 0e7edd8f..1e222472 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -36,9 +36,6 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Whether to read/write the loaded asset to the asset cache. T Load(string assetName, LocalizedContentManager.LanguageCode language, bool useCache); - /// Perform any cleanup needed when the locale changes. - void OnLocaleChanged(); - /// Normalize path separators in a file path. For asset keys, see instead. /// The file path to normalize. [Pure] @@ -69,5 +66,12 @@ namespace StardewModdingAPI.Framework.ContentManagers /// 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. IDictionary InvalidateCache(Func predicate, bool dispose = false); + + /// Perform any cleanup needed when the locale changes. + void OnLocaleChanged(); + + /// Clean up when the player is returning to the title screen. + /// This is called after the player returns to the title screen, but before runs. + void OnReturningToTitleScreen(); } } -- cgit