remove aggressive memory optimizations option

This commit is contained in:
Jesse Plamondon-Willard 2022-05-04 21:02:41 -04:00
parent c1342bd4cd
commit 295ad29b8d
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
10 changed files with 27 additions and 106 deletions

View File

@ -3,8 +3,10 @@
# Release notes # Release notes
## Upcoming release ## Upcoming release
* For players: * For players:
* Case-insensitive file paths (introduced in 3.14.0) are now disabled by default. * Disabled case-insensitive file paths (introduced in 3.14.0) 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._ _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. * Updated compatibility list.
## 3.14.0 ## 3.14.0

View File

@ -32,9 +32,6 @@ namespace StardewModdingAPI.Framework
/// <summary>An asset key prefix for assets from SMAPI mod folders.</summary> /// <summary>An asset key prefix for assets from SMAPI mod folders.</summary>
private readonly string ManagedPrefix = "SMAPI"; private readonly string ManagedPrefix = "SMAPI";
/// <summary>Whether to enable more aggressive memory optimizations.</summary>
private readonly bool AggressiveMemoryOptimizations;
/// <summary>Get a file path lookup for the given directory.</summary> /// <summary>Get a file path lookup for the given directory.</summary>
private readonly Func<string, IFilePathLookup> GetFilePathLookup; private readonly Func<string, IFilePathLookup> GetFilePathLookup;
@ -118,13 +115,11 @@ namespace StardewModdingAPI.Framework
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param> /// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
/// <param name="onLoadingFirstAsset">A callback to invoke the first time *any* game content manager loads an asset.</param> /// <param name="onLoadingFirstAsset">A callback to invoke the first time *any* game content manager loads an asset.</param>
/// <param name="onAssetLoaded">A callback to invoke when an asset is fully loaded.</param> /// <param name="onAssetLoaded">A callback to invoke when an asset is fully loaded.</param>
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
/// <param name="getFilePathLookup">Get a file path lookup for the given directory.</param> /// <param name="getFilePathLookup">Get a file path lookup for the given directory.</param>
/// <param name="onAssetsInvalidated">A callback to invoke when any asset names have been invalidated from the cache.</param> /// <param name="onAssetsInvalidated">A callback to invoke when any asset names have been invalidated from the cache.</param>
/// <param name="requestAssetOperations">Get the load/edit operations to apply to an asset by querying registered <see cref="IContentEvents.AssetRequested"/> event handlers.</param> /// <param name="requestAssetOperations">Get the load/edit operations to apply to an asset by querying registered <see cref="IContentEvents.AssetRequested"/> event handlers.</param>
public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, bool aggressiveMemoryOptimizations, Func<string, IFilePathLookup> getFilePathLookup, Action<IList<IAssetName>> onAssetsInvalidated, Func<IAssetInfo, IList<AssetOperationGroup>> requestAssetOperations) public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, Func<string, IFilePathLookup> getFilePathLookup, Action<IList<IAssetName>> onAssetsInvalidated, Func<IAssetInfo, IList<AssetOperationGroup>> requestAssetOperations)
{ {
this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations;
this.GetFilePathLookup = getFilePathLookup; this.GetFilePathLookup = getFilePathLookup;
this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor));
this.Reflection = reflection; this.Reflection = reflection;
@ -145,26 +140,11 @@ namespace StardewModdingAPI.Framework
reflection: reflection, reflection: reflection,
onDisposing: this.OnDisposing, onDisposing: this.OnDisposing,
onLoadingFirstAsset: onLoadingFirstAsset, onLoadingFirstAsset: onLoadingFirstAsset,
onAssetLoaded: onAssetLoaded, onAssetLoaded: onAssetLoaded
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,
onAssetLoaded: onAssetLoaded,
aggressiveMemoryOptimizations: aggressiveMemoryOptimizations
);
this.ContentManagers.Add(contentManagerForAssetPropagation);
this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory); 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<Dictionary<string, LocalizedContentManager.LanguageCode>>(() => this.GetLocaleCodes(customLanguages: Enumerable.Empty<ModLanguage>())); this.LocaleCodes = new Lazy<Dictionary<string, LocalizedContentManager.LanguageCode>>(() => this.GetLocaleCodes(customLanguages: Enumerable.Empty<ModLanguage>()));
} }
@ -184,8 +164,7 @@ namespace StardewModdingAPI.Framework
reflection: this.Reflection, reflection: this.Reflection,
onDisposing: this.OnDisposing, onDisposing: this.OnDisposing,
onLoadingFirstAsset: this.OnLoadingFirstAsset, onLoadingFirstAsset: this.OnLoadingFirstAsset,
onAssetLoaded: this.OnAssetLoaded, onAssetLoaded: this.OnAssetLoaded
aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations
); );
this.ContentManagers.Add(manager); this.ContentManagers.Add(manager);
return manager; return manager;
@ -213,7 +192,6 @@ namespace StardewModdingAPI.Framework
reflection: this.Reflection, reflection: this.Reflection,
jsonHelper: this.JsonHelper, jsonHelper: this.JsonHelper,
onDisposing: this.OnDisposing, onDisposing: this.OnDisposing,
aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations,
relativePathLookup: this.GetFilePathLookup(rootDirectory) relativePathLookup: this.GetFilePathLookup(rootDirectory)
); );
this.ContentManagers.Add(manager); this.ContentManagers.Add(manager);

View File

@ -11,7 +11,6 @@ using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Reflection;
using StardewValley; using StardewValley;
using xTile;
namespace StardewModdingAPI.Framework.ContentManagers namespace StardewModdingAPI.Framework.ContentManagers
{ {
@ -33,9 +32,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Simplifies access to private code.</summary> /// <summary>Simplifies access to private code.</summary>
protected readonly Reflector Reflection; protected readonly Reflector Reflection;
/// <summary>Whether to enable more aggressive memory optimizations.</summary>
protected readonly bool AggressiveMemoryOptimizations;
/// <summary>Whether to automatically try resolving keys to a localized form if available.</summary> /// <summary>Whether to automatically try resolving keys to a localized form if available.</summary>
protected bool TryLocalizeKeys = true; protected bool TryLocalizeKeys = true;
@ -82,8 +78,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="reflection">Simplifies access to private code.</param> /// <param name="reflection">Simplifies access to private code.</param>
/// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param> /// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param>
/// <param name="isNamespaced">Whether this content manager handles managed asset keys (e.g. to load assets from a mod folder).</param> /// <param name="isNamespaced">Whether this content manager handles managed asset keys (e.g. to load assets from a mod folder).</param>
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param> protected BaseContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action<BaseContentManager> onDisposing, bool isNamespaced)
protected BaseContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action<BaseContentManager> onDisposing, bool isNamespaced, bool aggressiveMemoryOptimizations)
: base(serviceProvider, rootDirectory, currentCulture) : base(serviceProvider, rootDirectory, currentCulture)
{ {
// init // init
@ -95,7 +90,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
this.Reflection = reflection; this.Reflection = reflection;
this.OnDisposing = onDisposing; this.OnDisposing = onDisposing;
this.IsNamespaced = isNamespaced; this.IsNamespaced = isNamespaced;
this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations;
// get asset data // get asset data
this.BaseDisposableReferences = reflection.GetField<List<IDisposable>?>(this, "disposableAssets").GetValue() this.BaseDisposableReferences = reflection.GetField<List<IDisposable>?>(this, "disposableAssets").GetValue()
@ -231,14 +225,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
removeAssets[baseAssetName] = asset; removeAssets[baseAssetName] = asset;
remove = true; remove = true;
} }
// dispose if safe
if (remove && this.AggressiveMemoryOptimizations)
{
if (asset is Map map)
map.DisposeTileSheets(Game1.mapDisplayDevice);
}
return remove; return remove;
}, dispose); }, dispose);

View File

@ -51,9 +51,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param> /// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param>
/// <param name="onLoadingFirstAsset">A callback to invoke the first time *any* game content manager loads an asset.</param> /// <param name="onLoadingFirstAsset">A callback to invoke the first time *any* game content manager loads an asset.</param>
/// <param name="onAssetLoaded">A callback to invoke when an asset is fully loaded.</param> /// <param name="onAssetLoaded">A callback to invoke when an asset is fully loaded.</param>
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param> public GameContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action<BaseContentManager> onDisposing, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded)
public GameContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action<BaseContentManager> onDisposing, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, bool aggressiveMemoryOptimizations) : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: false)
: base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: false, aggressiveMemoryOptimizations: aggressiveMemoryOptimizations)
{ {
this.OnLoadingFirstAsset = onLoadingFirstAsset; this.OnLoadingFirstAsset = onLoadingFirstAsset;
this.OnAssetLoaded = onAssetLoaded; this.OnAssetLoaded = onAssetLoaded;

View File

@ -21,8 +21,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
** Public methods ** Public methods
*********/ *********/
/// <inheritdoc /> /// <inheritdoc />
public GameContentManagerForAssetPropagation(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action<BaseContentManager> onDisposing, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, bool aggressiveMemoryOptimizations) public GameContentManagerForAssetPropagation(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action<BaseContentManager> onDisposing, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded)
: base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, onLoadingFirstAsset, onAssetLoaded, aggressiveMemoryOptimizations) { } : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, onLoadingFirstAsset, onAssetLoaded) { }
/// <inheritdoc /> /// <inheritdoc />
public override T LoadExact<T>(IAssetName assetName, bool useCache) public override T LoadExact<T>(IAssetName assetName, bool useCache)

View File

@ -55,10 +55,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="reflection">Simplifies access to private code.</param> /// <param name="reflection">Simplifies access to private code.</param>
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param> /// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
/// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param> /// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param>
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
/// <param name="relativePathLookup">A lookup for relative paths within the <paramref name="rootDirectory"/>.</param> /// <param name="relativePathLookup">A lookup for relative paths within the <paramref name="rootDirectory"/>.</param>
public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing, bool aggressiveMemoryOptimizations, IFilePathLookup relativePathLookup) public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing, IFilePathLookup relativePathLookup)
: base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true, aggressiveMemoryOptimizations: aggressiveMemoryOptimizations) : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true)
{ {
this.GameContentManager = gameContentManager; this.GameContentManager = gameContentManager;
this.RelativePathLookup = relativePathLookup; this.RelativePathLookup = relativePathLookup;

View File

@ -22,7 +22,6 @@ namespace StardewModdingAPI.Framework.Models
[nameof(VerboseLogging)] = false, [nameof(VerboseLogging)] = false,
[nameof(LogNetworkTraffic)] = false, [nameof(LogNetworkTraffic)] = false,
[nameof(RewriteMods)] = true, [nameof(RewriteMods)] = true,
[nameof(AggressiveMemoryOptimizations)] = false,
[nameof(UsePintail)] = true, [nameof(UsePintail)] = true,
[nameof(UseCaseInsensitivePaths)] = false [nameof(UseCaseInsensitivePaths)] = false
}; };
@ -63,9 +62,6 @@ namespace StardewModdingAPI.Framework.Models
/// <summary>Whether SMAPI should rewrite mods for compatibility.</summary> /// <summary>Whether SMAPI should rewrite mods for compatibility.</summary>
public bool RewriteMods { get; } public bool RewriteMods { get; }
/// <summary>Whether to enable more aggressive memory optimizations.</summary>
public bool AggressiveMemoryOptimizations { get; }
/// <summary>Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</summary> /// <summary>Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</summary>
public bool UsePintail { get; } public bool UsePintail { get; }
@ -94,13 +90,12 @@ namespace StardewModdingAPI.Framework.Models
/// <param name="webApiBaseUrl">The base URL for SMAPI's web API, used to perform update checks.</param> /// <param name="webApiBaseUrl">The base URL for SMAPI's web API, used to perform update checks.</param>
/// <param name="verboseLogging">Whether SMAPI should log more information about the game context.</param> /// <param name="verboseLogging">Whether SMAPI should log more information about the game context.</param>
/// <param name="rewriteMods">Whether SMAPI should rewrite mods for compatibility.</param> /// <param name="rewriteMods">Whether SMAPI should rewrite mods for compatibility.</param>
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
/// <param name="usePintail">Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</param> /// <param name="usePintail">Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</param>
/// <param name="useCaseInsensitivePaths">>Whether to make SMAPI file APIs case-insensitive, even on Linux.</param> /// <param name="useCaseInsensitivePaths">>Whether to make SMAPI file APIs case-insensitive, even on Linux.</param>
/// <param name="logNetworkTraffic">Whether SMAPI should log network traffic.</param> /// <param name="logNetworkTraffic">Whether SMAPI should log network traffic.</param>
/// <param name="consoleColors">The colors to use for text written to the SMAPI console.</param> /// <param name="consoleColors">The colors to use for text written to the SMAPI console.</param>
/// <param name="suppressUpdateChecks">The mod IDs SMAPI should ignore when performing update checks or validating update keys.</param> /// <param name="suppressUpdateChecks">The mod IDs SMAPI should ignore when performing update checks or validating update keys.</param>
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.DeveloperMode = developerMode;
this.CheckForUpdates = checkForUpdates; this.CheckForUpdates = checkForUpdates;
@ -110,7 +105,6 @@ namespace StardewModdingAPI.Framework.Models
this.WebApiBaseUrl = webApiBaseUrl; this.WebApiBaseUrl = webApiBaseUrl;
this.VerboseLogging = verboseLogging; this.VerboseLogging = verboseLogging;
this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(SConfig.RewriteMods)]; 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.UsePintail = usePintail ?? (bool)SConfig.DefaultValues[nameof(SConfig.UsePintail)];
this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(SConfig.UseCaseInsensitivePaths)]; this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(SConfig.UseCaseInsensitivePaths)];
this.LogNetworkTraffic = logNetworkTraffic; this.LogNetworkTraffic = logNetworkTraffic;

View File

@ -1253,7 +1253,6 @@ namespace StardewModdingAPI.Framework
onLoadingFirstAsset: this.InitializeBeforeFirstAssetLoaded, onLoadingFirstAsset: this.InitializeBeforeFirstAssetLoaded,
onAssetLoaded: this.OnAssetLoaded, onAssetLoaded: this.OnAssetLoaded,
onAssetsInvalidated: this.OnAssetsInvalidated, onAssetsInvalidated: this.OnAssetsInvalidated,
aggressiveMemoryOptimizations: this.Settings.AggressiveMemoryOptimizations,
getFilePathLookup: this.GetFilePathLookup, getFilePathLookup: this.GetFilePathLookup,
requestAssetOperations: this.RequestAssetOperations requestAssetOperations: this.RequestAssetOperations
); );

View File

@ -5,7 +5,6 @@ using System.IO;
using System.Linq; using System.Linq;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Internal; using StardewModdingAPI.Internal;
using StardewModdingAPI.Toolkit.Utilities; using StardewModdingAPI.Toolkit.Utilities;
@ -33,18 +32,12 @@ namespace StardewModdingAPI.Metadata
/// <summary>The main content manager through which to reload assets.</summary> /// <summary>The main content manager through which to reload assets.</summary>
private readonly LocalizedContentManager MainContentManager; private readonly LocalizedContentManager MainContentManager;
/// <summary>An internal content manager used only for asset propagation. See remarks on <see cref="GameContentManagerForAssetPropagation"/>.</summary>
private readonly GameContentManagerForAssetPropagation DisposableContentManager;
/// <summary>Writes messages to the console.</summary> /// <summary>Writes messages to the console.</summary>
private readonly IMonitor Monitor; private readonly IMonitor Monitor;
/// <summary>Simplifies access to private game code.</summary> /// <summary>Simplifies access to private game code.</summary>
private readonly Reflector Reflection; private readonly Reflector Reflection;
/// <summary>Whether to enable more aggressive memory optimizations.</summary>
private readonly bool AggressiveMemoryOptimizations;
/// <summary>Parse a raw asset name.</summary> /// <summary>Parse a raw asset name.</summary>
private readonly Func<string, IAssetName> ParseAssetName; private readonly Func<string, IAssetName> ParseAssetName;
@ -67,18 +60,14 @@ namespace StardewModdingAPI.Metadata
*********/ *********/
/// <summary>Initialize the core asset data.</summary> /// <summary>Initialize the core asset data.</summary>
/// <param name="mainContent">The main content manager through which to reload assets.</param> /// <param name="mainContent">The main content manager through which to reload assets.</param>
/// <param name="disposableContent">An internal content manager used only for asset propagation.</param>
/// <param name="monitor">Writes messages to the console.</param> /// <param name="monitor">Writes messages to the console.</param>
/// <param name="reflection">Simplifies access to private code.</param> /// <param name="reflection">Simplifies access to private code.</param>
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
/// <param name="parseAssetName">Parse a raw asset name.</param> /// <param name="parseAssetName">Parse a raw asset name.</param>
public CoreAssetPropagator(LocalizedContentManager mainContent, GameContentManagerForAssetPropagation disposableContent, IMonitor monitor, Reflector reflection, bool aggressiveMemoryOptimizations, Func<string, IAssetName> parseAssetName) public CoreAssetPropagator(LocalizedContentManager mainContent, IMonitor monitor, Reflector reflection, Func<string, IAssetName> parseAssetName)
{ {
this.MainContentManager = mainContent; this.MainContentManager = mainContent;
this.DisposableContentManager = disposableContent;
this.Monitor = monitor; this.Monitor = monitor;
this.Reflection = reflection; this.Reflection = reflection;
this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations;
this.ParseAssetName = parseAssetName; this.ParseAssetName = parseAssetName;
} }
@ -230,7 +219,7 @@ namespace StardewModdingAPI.Metadata
** Buildings ** Buildings
****/ ****/
case "buildings/houses": // Farm case "buildings/houses": // Farm
Farm.houseTextures = this.LoadAndDisposeIfNeeded(Farm.houseTextures, key); Farm.houseTextures = this.LoadTexture(key);
return true; return true;
case "buildings/houses_paintmask": // Farm case "buildings/houses_paintmask": // Farm
@ -247,7 +236,7 @@ namespace StardewModdingAPI.Metadata
** Content\Characters\Farmer ** Content\Characters\Farmer
****/ ****/
case "characters/farmer/accessories": // Game1.LoadContent case "characters/farmer/accessories": // Game1.LoadContent
FarmerRenderer.accessoriesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.accessoriesTexture, key); FarmerRenderer.accessoriesTexture = this.LoadTexture(key);
return true; return true;
case "characters/farmer/farmer_base": // Farmer case "characters/farmer/farmer_base": // Farmer
@ -257,19 +246,19 @@ namespace StardewModdingAPI.Metadata
return !ignoreWorld && this.ReloadPlayerSprites(assetName); return !ignoreWorld && this.ReloadPlayerSprites(assetName);
case "characters/farmer/hairstyles": // Game1.LoadContent case "characters/farmer/hairstyles": // Game1.LoadContent
FarmerRenderer.hairStylesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hairStylesTexture, key); FarmerRenderer.hairStylesTexture = this.LoadTexture(key);
return true; return true;
case "characters/farmer/hats": // Game1.LoadContent case "characters/farmer/hats": // Game1.LoadContent
FarmerRenderer.hatsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hatsTexture, key); FarmerRenderer.hatsTexture = this.LoadTexture(key);
return true; return true;
case "characters/farmer/pants": // Game1.LoadContent case "characters/farmer/pants": // Game1.LoadContent
FarmerRenderer.pantsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.pantsTexture, key); FarmerRenderer.pantsTexture = this.LoadTexture(key);
return true; return true;
case "characters/farmer/shirts": // Game1.LoadContent case "characters/farmer/shirts": // Game1.LoadContent
FarmerRenderer.shirtsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.shirtsTexture, key); FarmerRenderer.shirtsTexture = this.LoadTexture(key);
return true; return true;
/**** /****
@ -905,9 +894,6 @@ namespace StardewModdingAPI.Metadata
GameLocation location = locationInfo.Location; GameLocation location = locationInfo.Location;
Vector2? playerPos = Game1.player?.Position; Vector2? playerPos = Game1.player?.Position;
if (this.AggressiveMemoryOptimizations)
location.map.DisposeTileSheets(Game1.mapDisplayDevice);
// reload map // reload map
location.interiorDoors.Clear(); // prevent errors when doors try to update tiles which no longer exist location.interiorDoors.Clear(); // prevent errors when doors try to update tiles which no longer exist
location.reloadMap(); location.reloadMap();
@ -973,7 +959,7 @@ namespace StardewModdingAPI.Metadata
// update sprite // update sprite
foreach (var target in characters) 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; propagated[target.AssetName] = true;
} }
} }
@ -1012,7 +998,7 @@ namespace StardewModdingAPI.Metadata
// update portrait // update portrait
foreach (var target in characters) 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; propagated[target.AssetName] = true;
} }
} }
@ -1284,25 +1270,10 @@ namespace StardewModdingAPI.Metadata
: Array.Empty<string>(); : Array.Empty<string>();
} }
/// <summary>Load a texture, and dispose the old one if <see cref="AggressiveMemoryOptimizations"/> is enabled and it's different from the new instance.</summary> /// <summary>Load a texture from the main content manager.</summary>
/// <param name="oldTexture">The previous texture to dispose.</param>
/// <param name="key">The asset key to load.</param> /// <param name="key">The asset key to load.</param>
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<Texture2D>(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<Texture2D>(key); return this.MainContentManager.Load<Texture2D>(key);
} }

View File

@ -39,13 +39,6 @@ copy all the settings, or you may cause bugs due to overridden changes in future
*/ */
"RewriteMods": true, "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. * Whether to make SMAPI file APIs case-insensitive, even on Linux.
* This is experimental, and the initial implementation may impact load times. * This is experimental, and the initial implementation may impact load times.