diff --git a/SmithingPlus/Common/Metal/MetalMaterialExtensions.cs b/SmithingPlus/Common/Metal/MetalMaterialExtensions.cs index 32a8749..32efffe 100644 --- a/SmithingPlus/Common/Metal/MetalMaterialExtensions.cs +++ b/SmithingPlus/Common/Metal/MetalMaterialExtensions.cs @@ -15,8 +15,13 @@ public static class MetalMaterialExtensions public static MetalMaterial? GetOrCacheMetalMaterial(this CollectibleObject collObj, ICoreAPI api) { - var metalMaterial = - CacheHelper.GetOrAdd(Core.MetalMaterialCache, collObj.Code, () => collObj.GetMetalMaterial(api)); + var cache = Core.MetalMaterialCache; + if (cache.TryGetValue(collObj.Code, out var cached)) return cached; + var metalMaterial = collObj.GetMetalMaterial(api); + // Negative results are only meaningful once the loader has resolved its materials; + // before that point every lookup returns null and must not poison the cache. + if (metalMaterial != null || api.GetModSystem()?.MaterialsResolved == true) + cache[collObj.Code] = metalMaterial; return metalMaterial; } @@ -144,10 +149,10 @@ public static bool HasMetalMaterialSimple(this CollectibleObject collObj) public static MetalMaterial? GetOrCacheMetalMaterial(this ItemStack itemStack, ICoreAPI api) { var collObj = itemStack.Collectible; - if (collObj is not IAnvilWorkable anvilWorkable) return collObj?.GetMetalMaterial(api); + if (collObj is not IAnvilWorkable anvilWorkable) return collObj?.GetOrCacheMetalMaterial(api); var ingotStack = anvilWorkable.GetBaseMaterial(itemStack); var metalMaterial = ingotStack.Collectible.GetOrCacheMetalMaterial(api); - return metalMaterial ?? collObj.GetMetalMaterial(api); + return metalMaterial ?? collObj.GetOrCacheMetalMaterial(api); } // Use when what matters is the processed result (e.g., iron bloom > iron, blister steel > steel) diff --git a/SmithingPlus/Common/Metal/MetalMaterialLoader.cs b/SmithingPlus/Common/Metal/MetalMaterialLoader.cs index c64c79c..caec8e6 100644 --- a/SmithingPlus/Common/Metal/MetalMaterialLoader.cs +++ b/SmithingPlus/Common/Metal/MetalMaterialLoader.cs @@ -14,6 +14,7 @@ public class MetalMaterialLoader : ModSystem { private readonly Dictionary _metalMaterials = new(); public Dictionary ResolvedMaterials { get; private set; } = new(); + public bool MaterialsResolved { get; private set; } public override double ExecuteOrder() { @@ -81,6 +82,7 @@ public override void AssetsFinalize(ICoreAPI api) ResolvedMaterials = _metalMaterials .Where(kvp => kvp.Value.Resolved) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + MaterialsResolved = true; Core.Logger.Notification("[MetalMaterial] Done resolving metal materials."); Core.Logger.Notification( $"[MetalMaterial] Resolved {resolvedCount} out of {_metalMaterials.Count} metal materials."); diff --git a/SmithingPlus/Util/CollectibleExtensions.cs b/SmithingPlus/Util/CollectibleExtensions.cs index 30bec46..ff22a01 100644 --- a/SmithingPlus/Util/CollectibleExtensions.cs +++ b/SmithingPlus/Util/CollectibleExtensions.cs @@ -73,33 +73,58 @@ public static bool MatchesToolHeadSelector(this CollectibleObject collObj, bool public static SmithingRecipe? GetSmithingRecipe(this CollectibleObject collObj, ICoreAPI api) { - var smithingRecipe = api.ModLoader - .GetModSystem() - .SmithingRecipes - .FirstOrDefault(r => r.Output.ResolvedItemstack.Collectible.Code.Equals(collObj.Code)); - return smithingRecipe; + var byOutput = ObjectCacheUtil.GetOrCreate(api, $"{Core.ModId}:smithingRecipesByOutput", () => + { + var dict = new Dictionary(); + foreach (var recipe in api.ModLoader.GetModSystem().SmithingRecipes) + { + var code = recipe?.Output?.ResolvedItemstack?.Collectible?.Code; + if (code != null) dict.TryAdd(code, recipe!); + } + + return dict; + }); + return byOutput.TryGetValue(collObj.Code, out var smithingRecipe) ? smithingRecipe : null; } public static IEnumerable GetSmithingRecipesAsIngredient(this CollectibleObject collObj, ICoreAPI api) { - var smithingRecipes = - from recipe in api.ModLoader.GetModSystem().SmithingRecipes - from ing in recipe.Ingredients - where ing.ResolvedItemStack?.Collectible?.Code?.Equals(collObj.Code) is true - select recipe; - return smithingRecipes; + var byIngredient = ObjectCacheUtil.GetOrCreate(api, $"{Core.ModId}:smithingRecipesByIngredient", () => + { + var dict = new Dictionary>(); + foreach (var recipe in api.ModLoader.GetModSystem().SmithingRecipes) + foreach (var ing in recipe.Ingredients) + { + var code = ing?.ResolvedItemStack?.Collectible?.Code; + if (code == null) continue; + if (!dict.TryGetValue(code, out var list)) dict[code] = list = []; + // Prevent duplicate entries when a recipe has the same ingredient multiple times + if (list.Count == 0 || list[^1] != recipe) list.Add(recipe); + } + + return dict; + }); + return byIngredient.TryGetValue(collObj.Code, out var recipes) ? recipes : []; } public static IEnumerable GetGridRecipesAsIngredient(this CollectibleObject collObj, ICoreAPI api) { - var gridRecipes = - from recipe in api.World.GridRecipes - from ing in recipe.RecipeIngredients - where ing is { ResolvedItemStack.Collectible: not null } && - ing.ResolvedItemStack?.Collectible?.Code?.Equals(collObj.Code) is true - select recipe; - return gridRecipes; + var byIngredient = ObjectCacheUtil.GetOrCreate(api, $"{Core.ModId}:gridRecipesByIngredient", () => + { + var dict = new Dictionary>(); + foreach (var recipe in api.World.GridRecipes) + foreach (var ing in recipe.RecipeIngredients) + { + var code = ing?.ResolvedItemStack?.Collectible?.Code; + if (code == null) continue; + if (!dict.TryGetValue(code, out var list)) dict[code] = list = []; + if (list.Count == 0 || list[^1] != recipe) list.Add(recipe); + } + + return dict; + }); + return byIngredient.TryGetValue(collObj.Code, out var recipes) ? recipes : []; } public static CollectibleObject? CollectibleWithVariant(this CollectibleObject collObj, string type, string value) @@ -138,4 +163,4 @@ public static T GetBehavior(this CollectibleObject collObj, bool withInherita { return behavior?.GetField("metalProps"); } -} \ No newline at end of file +}