diff options
-rw-r--r-- | src/SMAPI/Framework/Utilities/TickCacheDictionary.cs | 26 | ||||
-rw-r--r-- | src/SMAPI/Metadata/CoreAssetPropagator.cs | 123 |
2 files changed, 112 insertions, 37 deletions
diff --git a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs index 20d206e2..7732ace8 100644 --- a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs +++ b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs @@ -48,4 +48,30 @@ namespace StardewModdingAPI.Framework.Utilities return this.Cache.Remove(cacheKey); } } + + /// <summary>An in-memory dictionary cache that stores data for the duration of a game update tick.</summary> + /// <typeparam name="TKey">The dictionary key type.</typeparam> + internal class TickCacheDictionary<TKey> : TickCacheDictionary<TKey, object> + where TKey : notnull + { + /********* + ** Public methods + *********/ + /// <summary>Get a value from the cache, fetching it first if it's not cached yet.</summary> + /// <param name="cacheKey">The unique key for the cached value.</param> + /// <param name="get">Get the latest data if it's not in the cache yet.</param> + public TValue GetOrSet<TValue>(TKey cacheKey, Func<TValue> get) + { + object? value = base.GetOrSet(cacheKey, () => get()!); + + try + { + return (TValue)value; + } + catch (Exception ex) + { + throw new InvalidCastException($"Can't cast value of the '{cacheKey}' cache entry from {value?.GetType().FullName ?? "null"} to {typeof(TValue).FullName}.", ex); + } + } + } } diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index ce9ba7a8..8ed6b591 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -7,6 +7,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Reflection; +using StardewModdingAPI.Framework.Utilities; using StardewModdingAPI.Internal; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; @@ -58,6 +59,9 @@ namespace StardewModdingAPI.Metadata Other }; + /// <summary>A cache of world data fetched for the current tick.</summary> + private readonly TickCacheDictionary<string> WorldCache = new(); + /********* ** Public methods @@ -842,8 +846,7 @@ namespace StardewModdingAPI.Metadata { Grass[] grasses = ( - from location in this.GetLocations() - from grass in location.terrainFeatures.Values.OfType<Grass>() + from grass in this.GetTerrainFeatures().OfType<Grass>() where this.IsSameBaseName(assetName, grass.textureName()) select grass ) @@ -978,8 +981,9 @@ namespace StardewModdingAPI.Metadata /// <returns>Returns whether any references were updated.</returns> private bool UpdateTreeTextures(int type) { - Tree[] trees = this.GetLocations() - .SelectMany(p => p.terrainFeatures.Values.OfType<Tree>()) + Tree[] trees = this + .GetTerrainFeatures() + .OfType<Tree>() .Where(tree => tree.treeType.Value == type) .ToArray(); @@ -1188,63 +1192,108 @@ namespace StardewModdingAPI.Metadata /// <summary>Get all NPCs in the game (excluding farm animals).</summary> private IEnumerable<NPC> GetCharacters() { - foreach (NPC character in this.GetLocations().SelectMany(p => p.characters)) - yield return character; + return this.WorldCache.GetOrSet( + nameof(this.GetCharacters), + () => + { + List<NPC> characters = new(); - if (Game1.CurrentEvent?.actors != null) - { - foreach (NPC character in Game1.CurrentEvent.actors) - yield return character; - } + foreach (NPC character in this.GetLocations().SelectMany(p => p.characters)) + characters.Add(character); + + if (Game1.CurrentEvent?.actors != null) + { + foreach (NPC character in Game1.CurrentEvent.actors) + characters.Add(character); + } + + return characters; + } + ); } /// <summary>Get all farm animals in the game.</summary> private IEnumerable<FarmAnimal> GetFarmAnimals() { - foreach (GameLocation location in this.GetLocations()) - { - if (location is Farm farm) + return this.WorldCache.GetOrSet( + nameof(this.GetFarmAnimals), + () => { - foreach (FarmAnimal animal in farm.animals.Values) - yield return animal; + List<FarmAnimal> animals = new(); + + foreach (GameLocation location in this.GetLocations()) + { + if (location is Farm farm) + { + foreach (FarmAnimal animal in farm.animals.Values) + animals.Add(animal); + } + else if (location is AnimalHouse animalHouse) + { + foreach (FarmAnimal animal in animalHouse.animals.Values) + animals.Add(animal); + } + } + + return animals; } - else if (location is AnimalHouse animalHouse) - foreach (FarmAnimal animal in animalHouse.animals.Values) - yield return animal; - } + ); } /// <summary>Get all locations in the game.</summary> /// <param name="buildingInteriors">Whether to also get the interior locations for constructable buildings.</param> private IEnumerable<GameLocation> GetLocations(bool buildingInteriors = true) { - return this.GetLocationsWithInfo(buildingInteriors).Select(info => info.Location); + return this.WorldCache.GetOrSet( + $"{nameof(this.GetLocations)}_{buildingInteriors}", + () => this.GetLocationsWithInfo(buildingInteriors).Select(info => info.Location).ToArray() + ); } /// <summary>Get all locations in the game.</summary> /// <param name="buildingInteriors">Whether to also get the interior locations for constructable buildings.</param> private IEnumerable<LocationInfo> GetLocationsWithInfo(bool buildingInteriors = true) { - // get available root locations - IEnumerable<GameLocation> rootLocations = Game1.locations; - if (SaveGame.loaded?.locations != null) - rootLocations = rootLocations.Concat(SaveGame.loaded.locations); + return this.WorldCache.GetOrSet( + $"{nameof(this.GetLocationsWithInfo)}_{buildingInteriors}", + () => + { + List<LocationInfo> locations = new(); - // yield root + child locations - foreach (GameLocation location in rootLocations) - { - yield return new LocationInfo(location, null); + // get root locations + foreach (GameLocation location in Game1.locations) + locations.Add(new LocationInfo(location, null)); + if (SaveGame.loaded?.locations != null) + { + foreach (GameLocation location in SaveGame.loaded.locations) + locations.Add(new LocationInfo(location, null)); + } - if (buildingInteriors && location is BuildableGameLocation buildableLocation) - { - foreach (Building building in buildableLocation.buildings) + // get child locations + if (buildingInteriors) { - GameLocation? indoors = building.indoors.Value; - if (indoors != null) - yield return new LocationInfo(indoors, building); + foreach (BuildableGameLocation location in locations.Select(p => p.Location).OfType<BuildableGameLocation>().ToArray()) + { + foreach (Building building in location.buildings) + { + GameLocation indoors = building.indoors.Value; + if (indoors is not null) + locations.Add(new LocationInfo(indoors, building)); + } + } } - } - } + + return locations; + }); + } + + /// <summary>Get all terrain features in the game.</summary> + private IEnumerable<TerrainFeature> GetTerrainFeatures() + { + return this.WorldCache.GetOrSet( + $"{nameof(this.GetTerrainFeatures)}", + () => this.GetLocations().SelectMany(p => p.terrainFeatures.Values).ToArray() + ); } /// <summary>Get whether two asset names are equivalent if you ignore the locale code.</summary> |