diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-02-21 23:05:17 -0500 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-02-21 23:05:17 -0500 |
commit | db011ee751bdfb8bbd9abbeb706966db4c4e2461 (patch) | |
tree | 4c1ca1c2fb2db41ac0d8b337e2f17209b4ad66f5 /src/SMAPI/Metadata/CoreAssetPropagator.cs | |
parent | f1505b0ebe5c8fa7cf3c160231f0ed7d1bd6619a (diff) | |
parent | 9c4c10d2d22cbf67ccadbd35fdf1ffced0541cc2 (diff) | |
download | SMAPI-db011ee751bdfb8bbd9abbeb706966db4c4e2461.tar.gz SMAPI-db011ee751bdfb8bbd9abbeb706966db4c4e2461.tar.bz2 SMAPI-db011ee751bdfb8bbd9abbeb706966db4c4e2461.zip |
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI/Metadata/CoreAssetPropagator.cs')
-rw-r--r-- | src/SMAPI/Metadata/CoreAssetPropagator.cs | 116 |
1 files changed, 90 insertions, 26 deletions
diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 063804e0..8b591bc1 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,15 @@ namespace StardewModdingAPI.Metadata /********* ** Fields *********/ + /// <summary>The main content manager through which to reload assets.</summary> + 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>Whether to enable more aggressive memory optimizations.</summary> + private readonly bool AggressiveMemoryOptimizations; + /// <summary>Normalizes an asset key to match the cache key and assert that it's valid.</summary> private readonly Func<string, string> AssertAndNormalizeAssetName; @@ -53,19 +63,24 @@ namespace StardewModdingAPI.Metadata ** Public methods *********/ /// <summary>Initialize the core asset data.</summary> - /// <param name="assertAndNormalizeAssetName">Normalizes an asset key to match the cache key and assert that it's valid.</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="reflection">Simplifies access to private code.</param> - public CoreAssetPropagator(Func<string, string> assertAndNormalizeAssetName, Reflector reflection) + /// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param> + 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; } /// <summary>Reload one of the game's core assets (if applicable).</summary> - /// <param name="content">The content manager through which to reload the asset.</param> /// <param name="assets">The asset keys and types to reload.</param> /// <returns>Returns a lookup of asset names to whether they've been propagated.</returns> - public IDictionary<string, bool> Propagate(LocalizedContentManager content, IDictionary<string, Type> assets) + public IDictionary<string, bool> Propagate(IDictionary<string, Type> assets) { // group into optimized lists var buckets = assets.GroupBy(p => @@ -86,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; } } @@ -107,13 +122,13 @@ namespace StardewModdingAPI.Metadata ** Private methods *********/ /// <summary>Reload one of the game's core assets (if applicable).</summary> - /// <param name="content">The content manager through which to reload the asset.</param> /// <param name="key">The asset key to reload.</param> /// <param name="type">The asset type to reload.</param> /// <returns>Returns whether an asset was loaded. The return value may be true or false, or a non-null value for true.</returns> [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); /**** @@ -163,14 +178,19 @@ namespace StardewModdingAPI.Metadata ** Buildings ****/ case "buildings\\houses": // Farm - reflection.GetField<Texture2D>(typeof(Farm), nameof(Farm.houseTextures)).SetValue(content.Load<Texture2D>(key)); - return true; + { + var field = reflection.GetField<Texture2D>(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<Texture2D>(key); + FarmerRenderer.accessoriesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.accessoriesTexture, key); return true; case "characters\\farmer\\farmer_base": // Farmer @@ -180,19 +200,19 @@ namespace StardewModdingAPI.Metadata return this.ReloadPlayerSprites(key); case "characters\\farmer\\hairstyles": // Game1.LoadContent - FarmerRenderer.hairStylesTexture = content.Load<Texture2D>(key); + FarmerRenderer.hairStylesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hairStylesTexture, key); return true; case "characters\\farmer\\hats": // Game1.LoadContent - FarmerRenderer.hatsTexture = content.Load<Texture2D>(key); + FarmerRenderer.hatsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hatsTexture, key); return true; case "characters\\farmer\\pants": // Game1.LoadContent - FarmerRenderer.pantsTexture = content.Load<Texture2D>(key); + FarmerRenderer.pantsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.pantsTexture, key); return true; case "characters\\farmer\\shirts": // Game1.LoadContent - FarmerRenderer.shirtsTexture = content.Load<Texture2D>(key); + FarmerRenderer.shirtsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.shirtsTexture, key); return true; /**** @@ -432,8 +452,7 @@ namespace StardewModdingAPI.Metadata return true; case "tilesheets\\chairtiles": // Game1.LoadContent - MapSeat.mapChairTexture = content.Load<Texture2D>(key); - return true; + return this.ReloadChairTiles(content, key); case "tilesheets\\craftables": // Game1.LoadContent Game1.bigCraftableSpriteSheet = content.Load<Texture2D>(key); @@ -582,7 +601,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; @@ -671,6 +690,28 @@ namespace StardewModdingAPI.Metadata return false; } + /// <summary>Reload map seat textures.</summary> + /// <param name="content">The content manager through which to reload the asset.</param> + /// <param name="key">The asset key to reload.</param> + /// <returns>Returns whether any textures were reloaded.</returns> + private bool ReloadChairTiles(LocalizedContentManager content, string key) + { + MapSeat.mapChairTexture = content.Load<Texture2D>(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; + } + /// <summary>Reload critter textures.</summary> /// <param name="content">The content manager through which to reload the asset.</param> /// <param name="key">The asset key to reload.</param> @@ -785,6 +826,9 @@ namespace StardewModdingAPI.Metadata /// <param name="location">The location whose map to reload.</param> 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(); @@ -822,10 +866,9 @@ namespace StardewModdingAPI.Metadata } /// <summary>Reload the sprites for matching NPCs.</summary> - /// <param name="content">The content manager through which to reload the asset.</param> /// <param name="keys">The asset keys to reload.</param> /// <param name="propagated">The asset keys which have been propagated.</param> - private void ReloadNpcSprites(LocalizedContentManager content, IEnumerable<string> keys, IDictionary<string, bool> propagated) + private void ReloadNpcSprites(IEnumerable<string> keys, IDictionary<string, bool> propagated) { // get NPCs HashSet<string> lookup = new HashSet<string>(keys, StringComparer.OrdinalIgnoreCase); @@ -843,16 +886,15 @@ namespace StardewModdingAPI.Metadata // update sprite foreach (var target in characters) { - target.Npc.Sprite.spriteTexture = content.Load<Texture2D>(target.Key); + target.Npc.Sprite.spriteTexture = this.LoadAndDisposeIfNeeded(target.Npc.Sprite.spriteTexture, target.Key); propagated[target.Key] = true; } } /// <summary>Reload the portraits for matching NPCs.</summary> - /// <param name="content">The content manager through which to reload the asset.</param> /// <param name="keys">The asset key to reload.</param> /// <param name="propagated">The asset keys which have been propagated.</param> - private void ReloadNpcPortraits(LocalizedContentManager content, IEnumerable<string> keys, IDictionary<string, bool> propagated) + private void ReloadNpcPortraits(IEnumerable<string> keys, IDictionary<string, bool> propagated) { // get NPCs HashSet<string> lookup = new HashSet<string>(keys, StringComparer.OrdinalIgnoreCase); @@ -881,7 +923,7 @@ namespace StardewModdingAPI.Metadata // update portrait foreach (var target in characters) { - target.Npc.Portrait = content.Load<Texture2D>(target.Key); + target.Npc.Portrait = this.LoadAndDisposeIfNeeded(target.Npc.Portrait, target.Key); propagated[target.Key] = true; } } @@ -1146,5 +1188,27 @@ namespace StardewModdingAPI.Metadata { return this.GetSegments(path).Length; } + + /// <summary>Load a texture, and dispose the old one if <see cref="AggressiveMemoryOptimizations"/> is enabled and it's different from the new instance.</summary> + /// <param name="oldTexture">The previous texture to dispose.</param> + /// <param name="key">The asset key to load.</param> + private Texture2D LoadAndDisposeIfNeeded(Texture2D oldTexture, 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); + } } } |