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') 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