defer asset reload during propagation when possible

This commit is contained in:
Jesse Plamondon-Willard 2022-05-19 21:04:32 -04:00
parent 0a050622f6
commit 7332879351
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
2 changed files with 70 additions and 59 deletions

View File

@ -3,6 +3,7 @@
# Release notes # Release notes
## Upcoming release ## Upcoming release
* For players: * For players:
* Improved performance when mods change some asset types (including NPC portraits/sprites).
* Fixed CurseForge update checks for the new CurseForge API. * Fixed CurseForge update checks for the new CurseForge API.
## 3.14.4 ## 3.14.4

View File

@ -219,7 +219,7 @@ namespace StardewModdingAPI.Metadata
** Animals ** Animals
****/ ****/
case "animals/horse": case "animals/horse":
return !ignoreWorld && this.UpdatePetOrHorseSprites<Horse>(content, assetName); return !ignoreWorld && this.UpdatePetOrHorseSprites<Horse>(assetName);
/**** /****
** Buildings ** Buildings
@ -486,7 +486,7 @@ namespace StardewModdingAPI.Metadata
return true; return true;
case "tilesheets/critters": // Critter constructor case "tilesheets/critters": // Critter constructor
return !ignoreWorld && this.UpdateCritterTextures(content, assetName); return !ignoreWorld && this.UpdateCritterTextures(assetName);
case "tilesheets/crops": // Game1.LoadContent case "tilesheets/crops": // Game1.LoadContent
Game1.cropSpriteSheet = content.Load<Texture2D>(key); Game1.cropSpriteSheet = content.Load<Texture2D>(key);
@ -555,27 +555,27 @@ namespace StardewModdingAPI.Metadata
return true; return true;
case "terrainfeatures/mushroom_tree": // from Tree case "terrainfeatures/mushroom_tree": // from Tree
return !ignoreWorld && this.UpdateTreeTextures(content, assetName, Tree.mushroomTree); return !ignoreWorld && this.UpdateTreeTextures(Tree.mushroomTree);
case "terrainfeatures/tree_palm": // from Tree case "terrainfeatures/tree_palm": // from Tree
return !ignoreWorld && this.UpdateTreeTextures(content, assetName, Tree.palmTree); return !ignoreWorld && this.UpdateTreeTextures(Tree.palmTree);
case "terrainfeatures/tree1_fall": // from Tree case "terrainfeatures/tree1_fall": // from Tree
case "terrainfeatures/tree1_spring": // from Tree case "terrainfeatures/tree1_spring": // from Tree
case "terrainfeatures/tree1_summer": // from Tree case "terrainfeatures/tree1_summer": // from Tree
case "terrainfeatures/tree1_winter": // from Tree case "terrainfeatures/tree1_winter": // from Tree
return !ignoreWorld && this.UpdateTreeTextures(content, assetName, Tree.bushyTree); return !ignoreWorld && this.UpdateTreeTextures(Tree.bushyTree);
case "terrainfeatures/tree2_fall": // from Tree case "terrainfeatures/tree2_fall": // from Tree
case "terrainfeatures/tree2_spring": // from Tree case "terrainfeatures/tree2_spring": // from Tree
case "terrainfeatures/tree2_summer": // from Tree case "terrainfeatures/tree2_summer": // from Tree
case "terrainfeatures/tree2_winter": // from Tree case "terrainfeatures/tree2_winter": // from Tree
return !ignoreWorld && this.UpdateTreeTextures(content, assetName, Tree.leafyTree); return !ignoreWorld && this.UpdateTreeTextures(Tree.leafyTree);
case "terrainfeatures/tree3_fall": // from Tree case "terrainfeatures/tree3_fall": // from Tree
case "terrainfeatures/tree3_spring": // from Tree case "terrainfeatures/tree3_spring": // from Tree
case "terrainfeatures/tree3_winter": // from Tree case "terrainfeatures/tree3_winter": // from Tree
return !ignoreWorld && this.UpdateTreeTextures(content, assetName, Tree.pineTree); return !ignoreWorld && this.UpdateTreeTextures(Tree.pineTree);
} }
/**** /****
@ -585,11 +585,11 @@ namespace StardewModdingAPI.Metadata
{ {
// dynamic textures // dynamic textures
if (assetName.StartsWith("animals/cat")) if (assetName.StartsWith("animals/cat"))
return this.UpdatePetOrHorseSprites<Cat>(content, assetName); return this.UpdatePetOrHorseSprites<Cat>(assetName);
if (assetName.StartsWith("animals/dog")) if (assetName.StartsWith("animals/dog"))
return this.UpdatePetOrHorseSprites<Dog>(content, assetName); return this.UpdatePetOrHorseSprites<Dog>(assetName);
if (assetName.IsDirectlyUnderPath("Animals")) if (assetName.IsDirectlyUnderPath("Animals"))
return this.UpdateFarmAnimalSprites(content, assetName); return this.UpdateFarmAnimalSprites(assetName);
if (assetName.IsDirectlyUnderPath("Buildings")) if (assetName.IsDirectlyUnderPath("Buildings"))
return this.UpdateBuildings(assetName); return this.UpdateBuildings(assetName);
@ -643,33 +643,29 @@ namespace StardewModdingAPI.Metadata
/// <summary>Update the sprites for matching pets or horses.</summary> /// <summary>Update the sprites for matching pets or horses.</summary>
/// <typeparam name="TAnimal">The animal type.</typeparam> /// <typeparam name="TAnimal">The animal type.</typeparam>
/// <param name="content">The content manager through which to update the asset.</param>
/// <param name="assetName">The asset name to update.</param> /// <param name="assetName">The asset name to update.</param>
/// <returns>Returns whether any references were updated.</returns> /// <returns>Returns whether any references were updated.</returns>
private bool UpdatePetOrHorseSprites<TAnimal>(LocalizedContentManager content, IAssetName assetName) private bool UpdatePetOrHorseSprites<TAnimal>(IAssetName assetName)
where TAnimal : NPC where TAnimal : NPC
{ {
// find matches // find matches
TAnimal[] animals = this.GetCharacters() TAnimal[] animals = this.GetCharacters()
.OfType<TAnimal>() .OfType<TAnimal>()
.Where(p => this.IsSameBaseName(assetName, p.Sprite?.Texture?.Name)) .Where(p => this.IsSameBaseName(assetName, p.Sprite?.spriteTexture?.Name))
.ToArray(); .ToArray();
if (!animals.Any())
return false;
// update sprites // update sprites
Texture2D texture = content.Load<Texture2D>(assetName.BaseName); bool changed = false;
foreach (TAnimal animal in animals) foreach (TAnimal animal in animals)
animal.Sprite.spriteTexture = texture; changed |= this.MarkSpriteDirty(animal.Sprite);
return true; return changed;
} }
/// <summary>Update the sprites for matching farm animals.</summary> /// <summary>Update the sprites for matching farm animals.</summary>
/// <param name="content">The content manager through which to update the asset.</param>
/// <param name="assetName">The asset name to update.</param> /// <param name="assetName">The asset name to update.</param>
/// <returns>Returns whether any references were updated.</returns> /// <returns>Returns whether any references were updated.</returns>
/// <remarks>Derived from <see cref="FarmAnimal.reload"/>.</remarks> /// <remarks>Derived from <see cref="FarmAnimal.reload"/>.</remarks>
private bool UpdateFarmAnimalSprites(LocalizedContentManager content, IAssetName assetName) private bool UpdateFarmAnimalSprites(IAssetName assetName)
{ {
// find matches // find matches
FarmAnimal[] animals = this.GetFarmAnimals().ToArray(); FarmAnimal[] animals = this.GetFarmAnimals().ToArray();
@ -677,7 +673,7 @@ namespace StardewModdingAPI.Metadata
return false; return false;
// update sprites // update sprites
Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(assetName.BaseName)); bool changed = true;
foreach (FarmAnimal animal in animals) foreach (FarmAnimal animal in animals)
{ {
// get expected key // get expected key
@ -690,9 +686,9 @@ namespace StardewModdingAPI.Metadata
// reload asset // reload asset
if (this.IsSameBaseName(assetName, expectedKey)) if (this.IsSameBaseName(assetName, expectedKey))
animal.Sprite.spriteTexture = texture.Value; changed |= this.MarkSpriteDirty(animal.Sprite);
} }
return texture.IsValueCreated; return changed;
} }
/// <summary>Update building textures.</summary> /// <summary>Update building textures.</summary>
@ -707,7 +703,7 @@ namespace StardewModdingAPI.Metadata
// get building type // get building type
string type = Path.GetFileName(assetName.BaseName); string type = Path.GetFileName(assetName.BaseName);
if (isPaintMask) if (isPaintMask)
type = type.Substring(0, type.Length - paintMaskSuffix.Length); type = type[..^paintMaskSuffix.Length];
// get buildings // get buildings
Building[] buildings = this.GetLocations(buildingInteriors: false) Building[] buildings = this.GetLocations(buildingInteriors: false)
@ -747,7 +743,7 @@ namespace StardewModdingAPI.Metadata
foreach (MapSeat seat in location.mapSeats.Where(p => p != null)) foreach (MapSeat seat in location.mapSeats.Where(p => p != null))
{ {
if (this.IsSameBaseName(assetName, seat._loadedTextureFile)) if (this.IsSameBaseName(assetName, seat._loadedTextureFile))
seat.overlayTexture = MapSeat.mapChairTexture; seat._loadedTextureFile = null;
} }
} }
} }
@ -756,10 +752,9 @@ namespace StardewModdingAPI.Metadata
} }
/// <summary>Update critter textures.</summary> /// <summary>Update critter textures.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
/// <param name="assetName">The asset name to update.</param> /// <param name="assetName">The asset name to update.</param>
/// <returns>Returns whether any references were updated.</returns> /// <returns>Returns whether any references were updated.</returns>
private bool UpdateCritterTextures(LocalizedContentManager content, IAssetName assetName) private bool UpdateCritterTextures(IAssetName assetName)
{ {
// get critters // get critters
Critter[] critters = Critter[] critters =
@ -767,19 +762,16 @@ namespace StardewModdingAPI.Metadata
from location in this.GetLocations() from location in this.GetLocations()
where location.critters != null where location.critters != null
from Critter critter in location.critters from Critter critter in location.critters
where this.IsSameBaseName(assetName, critter.sprite?.Texture?.Name) where this.IsSameBaseName(assetName, critter.sprite?.spriteTexture?.Name)
select critter select critter
) )
.ToArray(); .ToArray();
if (!critters.Any())
return false;
// update sprites // update sprites
Texture2D texture = content.Load<Texture2D>(assetName.BaseName); bool changed = false;
foreach (Critter entry in critters) foreach (Critter entry in critters)
entry.sprite.spriteTexture = texture; changed |= this.MarkSpriteDirty(entry.sprite);
return changed;
return true;
} }
/// <summary>Update the sprites for interior doors.</summary> /// <summary>Update the sprites for interior doors.</summary>
@ -814,7 +806,7 @@ namespace StardewModdingAPI.Metadata
private bool UpdateFenceTextures(IAssetName assetName) private bool UpdateFenceTextures(IAssetName assetName)
{ {
// get fence type (e.g. LooseSprites/Fence3 => 3) // get fence type (e.g. LooseSprites/Fence3 => 3)
if (!int.TryParse(this.GetSegments(assetName.BaseName)[1].Substring("Fence".Length), out int fenceType)) if (!int.TryParse(this.GetSegments(assetName.BaseName)[1]["Fence".Length..], out int fenceType))
return false; return false;
// get fences // get fences
@ -830,9 +822,16 @@ namespace StardewModdingAPI.Metadata
.ToArray(); .ToArray();
// update fence textures // update fence textures
bool changed = false;
foreach (Fence fence in fences) foreach (Fence fence in fences)
fence.fenceTexture = new Lazy<Texture2D>(fence.loadFenceTexture); {
return true; if (fence.fenceTexture.IsValueCreated)
{
fence.fenceTexture = new Lazy<Texture2D>(fence.loadFenceTexture);
changed = true;
}
}
return changed;
} }
/// <summary>Update tree textures.</summary> /// <summary>Update tree textures.</summary>
@ -850,15 +849,16 @@ namespace StardewModdingAPI.Metadata
) )
.ToArray(); .ToArray();
if (grasses.Any()) bool changed = false;
foreach (Grass grass in grasses)
{ {
Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(assetName.BaseName)); if (grass.texture.IsValueCreated)
foreach (Grass grass in grasses) {
grass.texture = texture; grass.texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(assetName.BaseName));
return true; changed = true;
}
} }
return changed;
return false;
} }
/// <summary>Update the sprites for matching NPCs.</summary> /// <summary>Update the sprites for matching NPCs.</summary>
@ -869,19 +869,17 @@ namespace StardewModdingAPI.Metadata
var characters = var characters =
( (
from npc in this.GetCharacters() from npc in this.GetCharacters()
let key = this.ParseAssetNameOrNull(npc.Sprite?.Texture?.Name)?.GetBaseAssetName() let key = this.ParseAssetNameOrNull(npc.Sprite?.spriteTexture?.Name)?.GetBaseAssetName()
where key != null && propagated.ContainsKey(key) where key != null && propagated.ContainsKey(key)
select new { Npc = npc, AssetName = key } select new { Npc = npc, AssetName = key }
) )
.ToArray(); .ToArray();
if (!characters.Any())
return;
// update sprite // update sprite
foreach (var target in characters) foreach (var target in characters)
{ {
target.Npc.Sprite.spriteTexture = this.LoadTexture(target.AssetName.BaseName); if (this.MarkSpriteDirty(target.Npc.Sprite))
propagated[target.AssetName] = true; propagated[target.AssetName] = true;
} }
} }
@ -919,7 +917,7 @@ namespace StardewModdingAPI.Metadata
// update portrait // update portrait
foreach (var target in characters) foreach (var target in characters)
{ {
target.Npc.Portrait = this.LoadTexture(target.AssetName.BaseName); target.Npc.resetPortrait();
propagated[target.AssetName] = true; propagated[target.AssetName] = true;
} }
} }
@ -976,26 +974,38 @@ namespace StardewModdingAPI.Metadata
} }
/// <summary>Update tree textures.</summary> /// <summary>Update tree textures.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
/// <param name="assetName">The asset name to update.</param>
/// <param name="type">The type to update.</param> /// <param name="type">The type to update.</param>
/// <returns>Returns whether any references were updated.</returns> /// <returns>Returns whether any references were updated.</returns>
private bool UpdateTreeTextures(LocalizedContentManager content, IAssetName assetName, int type) private bool UpdateTreeTextures(int type)
{ {
Tree[] trees = this.GetLocations() Tree[] trees = this.GetLocations()
.SelectMany(p => p.terrainFeatures.Values.OfType<Tree>()) .SelectMany(p => p.terrainFeatures.Values.OfType<Tree>())
.Where(tree => tree.treeType.Value == type) .Where(tree => tree.treeType.Value == type)
.ToArray(); .ToArray();
if (trees.Any()) bool changed = false;
foreach (Tree tree in trees)
{ {
Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(assetName.BaseName)); if (tree.texture.IsValueCreated)
foreach (Tree tree in trees) {
tree.texture = texture; this.Reflection.GetMethod(tree, "resetTexture").Invoke();
return true; changed = true;
}
} }
return changed;
}
return false; /// <summary>Mark an animated sprite's texture dirty, so it's reloaded next time it's rendered.</summary>
/// <param name="sprite">The animated sprite to change.</param>
/// <returns>Returns whether the sprite was changed.</returns>
private bool MarkSpriteDirty(AnimatedSprite sprite)
{
if (sprite.loadedTexture is null && sprite.spriteTexture is null)
return false;
sprite.loadedTexture = null;
sprite.spriteTexture = null;
return true;
} }
/**** /****