diff --git a/docs/release-notes.md b/docs/release-notes.md index 11cccee2..2fff0c58 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,8 +3,10 @@ # Release notes ## Upcoming release * For players: - * Case-insensitive file paths (introduced in 3.14.0) are now disabled by default. - _You can enable them via `smapi-internal/config.json` if needed. These will be re-enabled in a later version after reworking them to reduce performance impact._ + * Disabled case-insensitive file paths (introduced in 3.14.0) by default. + _You can enable them by editing `smapi-internal/config.json` if needed. They'll be re-enabled in a later version after they're reworked to reduce performance impact._ + * Removed experimental 'aggressive memory optimizations' option. + _This was disabled by default and is no longer needed in most cases. Memory usage will be better reduced by reworked asset propagation in the upcoming SMAPI 4.0.0._ * Updated compatibility list. ## 3.14.0 diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 1a82d194..ef442fbe 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -32,9 +32,6 @@ 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; - /// Get a file path lookup for the given directory. private readonly Func GetFilePathLookup; @@ -118,13 +115,11 @@ namespace StardewModdingAPI.Framework /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke the first time *any* game content manager loads an asset. /// A callback to invoke when an asset is fully loaded. - /// Whether to enable more aggressive memory optimizations. /// Get a file path lookup for the given directory. /// A callback to invoke when any asset names have been invalidated from the cache. /// Get the load/edit operations to apply to an asset by querying registered event handlers. - public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action onAssetLoaded, bool aggressiveMemoryOptimizations, Func getFilePathLookup, Action> onAssetsInvalidated, Func> requestAssetOperations) + public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action onAssetLoaded, Func getFilePathLookup, Action> onAssetsInvalidated, Func> requestAssetOperations) { - this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; this.GetFilePathLookup = getFilePathLookup; this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); this.Reflection = reflection; @@ -145,26 +140,11 @@ namespace StardewModdingAPI.Framework reflection: reflection, onDisposing: this.OnDisposing, onLoadingFirstAsset: onLoadingFirstAsset, - onAssetLoaded: onAssetLoaded, - aggressiveMemoryOptimizations: aggressiveMemoryOptimizations + onAssetLoaded: onAssetLoaded ) ); - var contentManagerForAssetPropagation = new GameContentManagerForAssetPropagation( - name: nameof(GameContentManagerForAssetPropagation), - serviceProvider: serviceProvider, - rootDirectory: rootDirectory, - currentCulture: currentCulture, - coordinator: this, - monitor: monitor, - reflection: reflection, - onDisposing: this.OnDisposing, - onLoadingFirstAsset: onLoadingFirstAsset, - onAssetLoaded: onAssetLoaded, - aggressiveMemoryOptimizations: aggressiveMemoryOptimizations - ); - this.ContentManagers.Add(contentManagerForAssetPropagation); this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory); - this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, contentManagerForAssetPropagation, this.Monitor, reflection, aggressiveMemoryOptimizations, name => this.ParseAssetName(name, allowLocales: true)); + this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, this.Monitor, reflection, name => this.ParseAssetName(name, allowLocales: true)); this.LocaleCodes = new Lazy>(() => this.GetLocaleCodes(customLanguages: Enumerable.Empty())); } @@ -184,8 +164,7 @@ namespace StardewModdingAPI.Framework reflection: this.Reflection, onDisposing: this.OnDisposing, onLoadingFirstAsset: this.OnLoadingFirstAsset, - onAssetLoaded: this.OnAssetLoaded, - aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations + onAssetLoaded: this.OnAssetLoaded ); this.ContentManagers.Add(manager); return manager; @@ -213,7 +192,6 @@ namespace StardewModdingAPI.Framework reflection: this.Reflection, jsonHelper: this.JsonHelper, onDisposing: this.OnDisposing, - aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations, relativePathLookup: this.GetFilePathLookup(rootDirectory) ); this.ContentManagers.Add(manager); diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index b2e3ec0f..db2934a0 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -11,7 +11,6 @@ using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewValley; -using xTile; namespace StardewModdingAPI.Framework.ContentManagers { @@ -33,9 +32,6 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Simplifies access to private code. protected readonly Reflector Reflection; - /// Whether to enable more aggressive memory optimizations. - protected readonly bool AggressiveMemoryOptimizations; - /// Whether to automatically try resolving keys to a localized form if available. protected bool TryLocalizeKeys = true; @@ -82,8 +78,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Simplifies access to private code. /// A callback to invoke when the content manager is being disposed. /// Whether this content manager handles managed asset keys (e.g. to load assets from a mod folder). - /// Whether to enable more aggressive memory optimizations. - protected BaseContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, bool isNamespaced, bool aggressiveMemoryOptimizations) + protected BaseContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, bool isNamespaced) : base(serviceProvider, rootDirectory, currentCulture) { // init @@ -95,7 +90,6 @@ namespace StardewModdingAPI.Framework.ContentManagers this.Reflection = reflection; this.OnDisposing = onDisposing; this.IsNamespaced = isNamespaced; - this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; // get asset data this.BaseDisposableReferences = reflection.GetField?>(this, "disposableAssets").GetValue() @@ -231,14 +225,6 @@ namespace StardewModdingAPI.Framework.ContentManagers removeAssets[baseAssetName] = asset; remove = true; } - - // dispose if safe - if (remove && this.AggressiveMemoryOptimizations) - { - if (asset is Map map) - map.DisposeTileSheets(Game1.mapDisplayDevice); - } - return remove; }, dispose); diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 083df454..c53040e1 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -51,9 +51,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// A callback to invoke when the content manager is being disposed. /// A callback to invoke the first time *any* game content manager loads an asset. /// A callback to invoke when an asset is fully loaded. - /// Whether to enable more aggressive memory optimizations. - public GameContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, Action onLoadingFirstAsset, Action onAssetLoaded, bool aggressiveMemoryOptimizations) - : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: false, aggressiveMemoryOptimizations: aggressiveMemoryOptimizations) + public GameContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, Action onLoadingFirstAsset, Action onAssetLoaded) + : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: false) { this.OnLoadingFirstAsset = onLoadingFirstAsset; this.OnAssetLoaded = onAssetLoaded; diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs index 1b0e1016..5c574a1a 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs @@ -21,8 +21,8 @@ namespace StardewModdingAPI.Framework.ContentManagers ** Public methods *********/ /// - public GameContentManagerForAssetPropagation(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, Action onLoadingFirstAsset, Action onAssetLoaded, bool aggressiveMemoryOptimizations) - : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, onLoadingFirstAsset, onAssetLoaded, aggressiveMemoryOptimizations) { } + public GameContentManagerForAssetPropagation(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, Action onLoadingFirstAsset, Action onAssetLoaded) + : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, onLoadingFirstAsset, onAssetLoaded) { } /// public override T LoadExact(IAssetName assetName, bool useCache) diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 91de769f..65dffd8b 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -55,10 +55,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Simplifies access to private code. /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke when the content manager is being disposed. - /// Whether to enable more aggressive memory optimizations. /// A lookup for relative paths within the . - public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing, bool aggressiveMemoryOptimizations, IFilePathLookup relativePathLookup) - : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true, aggressiveMemoryOptimizations: aggressiveMemoryOptimizations) + public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing, IFilePathLookup relativePathLookup) + : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true) { this.GameContentManager = gameContentManager; this.RelativePathLookup = relativePathLookup; diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index b4184abb..80d0d9ba 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -22,7 +22,6 @@ namespace StardewModdingAPI.Framework.Models [nameof(VerboseLogging)] = false, [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, - [nameof(AggressiveMemoryOptimizations)] = false, [nameof(UsePintail)] = true, [nameof(UseCaseInsensitivePaths)] = false }; @@ -63,9 +62,6 @@ namespace StardewModdingAPI.Framework.Models /// Whether SMAPI should rewrite mods for compatibility. public bool RewriteMods { get; } - /// Whether to enable more aggressive memory optimizations. - public bool AggressiveMemoryOptimizations { get; } - /// Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. public bool UsePintail { get; } @@ -94,13 +90,12 @@ namespace StardewModdingAPI.Framework.Models /// The base URL for SMAPI's web API, used to perform update checks. /// Whether SMAPI should log more information about the game context. /// Whether SMAPI should rewrite mods for compatibility. - /// Whether to enable more aggressive memory optimizations. /// Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. /// >Whether to make SMAPI file APIs case-insensitive, even on Linux. /// Whether SMAPI should log network traffic. /// The colors to use for text written to the SMAPI console. /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. - public SConfig(bool developerMode, bool checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, bool verboseLogging, bool? rewriteMods, bool? aggressiveMemoryOptimizations, bool? usePintail, bool? useCaseInsensitivePaths, bool logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks) + public SConfig(bool developerMode, bool checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, bool verboseLogging, bool? rewriteMods, bool? usePintail, bool? useCaseInsensitivePaths, bool logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates; @@ -110,7 +105,6 @@ namespace StardewModdingAPI.Framework.Models this.WebApiBaseUrl = webApiBaseUrl; this.VerboseLogging = verboseLogging; this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(SConfig.RewriteMods)]; - this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations ?? (bool)SConfig.DefaultValues[nameof(SConfig.AggressiveMemoryOptimizations)]; this.UsePintail = usePintail ?? (bool)SConfig.DefaultValues[nameof(SConfig.UsePintail)]; this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(SConfig.UseCaseInsensitivePaths)]; this.LogNetworkTraffic = logNetworkTraffic; diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 50765083..a9296d9b 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1253,7 +1253,6 @@ namespace StardewModdingAPI.Framework onLoadingFirstAsset: this.InitializeBeforeFirstAssetLoaded, onAssetLoaded: this.OnAssetLoaded, onAssetsInvalidated: this.OnAssetsInvalidated, - aggressiveMemoryOptimizations: this.Settings.AggressiveMemoryOptimizations, getFilePathLookup: this.GetFilePathLookup, requestAssetOperations: this.RequestAssetOperations ); diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 5dee2c4d..12b73515 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Internal; using StardewModdingAPI.Toolkit.Utilities; @@ -33,18 +32,12 @@ namespace StardewModdingAPI.Metadata /// 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; - /// Writes messages to the console. private readonly IMonitor Monitor; /// Simplifies access to private game code. private readonly Reflector Reflection; - /// Whether to enable more aggressive memory optimizations. - private readonly bool AggressiveMemoryOptimizations; - /// Parse a raw asset name. private readonly Func ParseAssetName; @@ -67,18 +60,14 @@ namespace StardewModdingAPI.Metadata *********/ /// Initialize the core asset data. /// The main content manager through which to reload assets. - /// An internal content manager used only for asset propagation. /// Writes messages to the console. /// Simplifies access to private code. - /// Whether to enable more aggressive memory optimizations. /// Parse a raw asset name. - public CoreAssetPropagator(LocalizedContentManager mainContent, GameContentManagerForAssetPropagation disposableContent, IMonitor monitor, Reflector reflection, bool aggressiveMemoryOptimizations, Func parseAssetName) + public CoreAssetPropagator(LocalizedContentManager mainContent, IMonitor monitor, Reflector reflection, Func parseAssetName) { this.MainContentManager = mainContent; - this.DisposableContentManager = disposableContent; this.Monitor = monitor; this.Reflection = reflection; - this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; this.ParseAssetName = parseAssetName; } @@ -230,7 +219,7 @@ namespace StardewModdingAPI.Metadata ** Buildings ****/ case "buildings/houses": // Farm - Farm.houseTextures = this.LoadAndDisposeIfNeeded(Farm.houseTextures, key); + Farm.houseTextures = this.LoadTexture(key); return true; case "buildings/houses_paintmask": // Farm @@ -247,7 +236,7 @@ namespace StardewModdingAPI.Metadata ** Content\Characters\Farmer ****/ case "characters/farmer/accessories": // Game1.LoadContent - FarmerRenderer.accessoriesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.accessoriesTexture, key); + FarmerRenderer.accessoriesTexture = this.LoadTexture(key); return true; case "characters/farmer/farmer_base": // Farmer @@ -257,19 +246,19 @@ namespace StardewModdingAPI.Metadata return !ignoreWorld && this.ReloadPlayerSprites(assetName); case "characters/farmer/hairstyles": // Game1.LoadContent - FarmerRenderer.hairStylesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hairStylesTexture, key); + FarmerRenderer.hairStylesTexture = this.LoadTexture(key); return true; case "characters/farmer/hats": // Game1.LoadContent - FarmerRenderer.hatsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hatsTexture, key); + FarmerRenderer.hatsTexture = this.LoadTexture(key); return true; case "characters/farmer/pants": // Game1.LoadContent - FarmerRenderer.pantsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.pantsTexture, key); + FarmerRenderer.pantsTexture = this.LoadTexture(key); return true; case "characters/farmer/shirts": // Game1.LoadContent - FarmerRenderer.shirtsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.shirtsTexture, key); + FarmerRenderer.shirtsTexture = this.LoadTexture(key); return true; /**** @@ -905,9 +894,6 @@ namespace StardewModdingAPI.Metadata GameLocation location = locationInfo.Location; Vector2? playerPos = Game1.player?.Position; - 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(); @@ -973,7 +959,7 @@ namespace StardewModdingAPI.Metadata // update sprite foreach (var target in characters) { - target.Npc.Sprite.spriteTexture = this.LoadAndDisposeIfNeeded(target.Npc.Sprite.spriteTexture, target.AssetName.BaseName); + target.Npc.Sprite.spriteTexture = this.LoadTexture(target.AssetName.BaseName); propagated[target.AssetName] = true; } } @@ -1012,7 +998,7 @@ namespace StardewModdingAPI.Metadata // update portrait foreach (var target in characters) { - target.Npc.Portrait = this.LoadAndDisposeIfNeeded(target.Npc.Portrait, target.AssetName.BaseName); + target.Npc.Portrait = this.LoadTexture(target.AssetName.BaseName); propagated[target.AssetName] = true; } } @@ -1284,25 +1270,10 @@ namespace StardewModdingAPI.Metadata : Array.Empty(); } - /// Load a texture, and dispose the old one if is enabled and it's different from the new instance. - /// The previous texture to dispose. + /// Load a texture from the main content manager. /// The asset key to load. - private Texture2D LoadAndDisposeIfNeeded(Texture2D? oldTexture, string key) + private Texture2D LoadTexture(string key) { - // 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.IsResponsibleFor(oldTexture)) - oldTexture.Dispose(); - - return newTexture; - } - - // else just (re)load it from the main content manager return this.MainContentManager.Load(key); } diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index 544aeaec..bdd6374a 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -39,13 +39,6 @@ copy all the settings, or you may cause bugs due to overridden changes in future */ "RewriteMods": true, - /** - * Whether to enable more aggressive memory optimizations. - * If you get frequent 'OutOfMemoryException' errors, you can try enabling this to reduce their - * frequency. This may cause crashes for farmhands in multiplayer. - */ - "AggressiveMemoryOptimizations": false, - /** * Whether to make SMAPI file APIs case-insensitive, even on Linux. * This is experimental, and the initial implementation may impact load times.