On our 194-mod server (VS 1.22.5, SmithingPlus 1.9.0-rc.1), every player's client freezes for roughly 50 seconds the first time they look at an anvil, once per anvil tier per session. I profiled it with a Harmony stopwatch wrapper and the cause is CollectibleBehaviorCastToolHead.GetRequiredAnvilTier resolving metal materials through an uncached path that re-scans the entire grid recipe registry on every call.
Vanilla BlockAnvil.GetPlacedBlockInteractionHelp builds its interaction help list by calling GetRequiredAnvilTier once per workable handbook stack. Two profiled builds on a live client, tier 4 anvil:
GetHandBookStacks: 1843 calls, 8ms total
GetRequiredAnvilTier: 1792 calls, 50989ms total
50988.2ms 1703 calls SmithingPlus.CastingTweaks.CollectibleBehaviorCastToolHead
0.4ms 27 calls SmithingPlus.SmithWithBits.CollectibleBehaviorWorkableNugget
0.1ms 14 calls SmithingPlus.Common.CollectibleBehaviorJsonAnvilWorkable
Second run after clearing the cache: 49006ms with the same distribution. Everything outside CollectibleBehaviorCastToolHead totals under 100ms. Per call the behavior costs about 30ms, and the pack has 1703 cast-tool stacks (armory and firearms mods add many castable tool variants).
Decompiled from 1.9.0-rc.1:
1:Cache bypass for behavior-based workables. MetalMaterialExtensions.GetOrCacheMetalMaterial(this ItemStack, ...) checks collectible as IAnvilWorkable. Cast tools carry workability as a behavior, so the collectible itself fails the check and the method falls through to the private uncached GetMetalMaterial(this CollectibleObject, ...) instead of the cached GetOrCacheMetalMaterial(this CollectibleObject, ...).
2:Null results are never cached. CacheHelper.GetOrAdd returns early on a null factory result without storing it. Cast tools whose variant is not a resolvable metal material (most modded ones) produce null, so even the cached entry point would recompute them on every call.
3:The fallback scan is unindexed. When the direct variant lookup fails, GetMetalMaterial falls through to GetSmithingRecipe (linear scan of all smithing recipes) and then GetGridRecipesAsIngredient (LINQ over every grid recipe times every ingredient, per call). On a large pack the grid scan alone is tens of milliseconds. This is the 30ms.
Multiply: 1703 stacks x full registry scan x once per anvil tier x every session x every player.
On our 194-mod server (VS 1.22.5, SmithingPlus 1.9.0-rc.1), every player's client freezes for roughly 50 seconds the first time they look at an anvil, once per anvil tier per session. I profiled it with a Harmony stopwatch wrapper and the cause is
CollectibleBehaviorCastToolHead.GetRequiredAnvilTierresolving metal materials through an uncached path that re-scans the entire grid recipe registry on every call.Vanilla
BlockAnvil.GetPlacedBlockInteractionHelpbuilds its interaction help list by callingGetRequiredAnvilTieronce per workable handbook stack. Two profiled builds on a live client, tier 4 anvil:Second run after clearing the cache: 49006ms with the same distribution. Everything outside
CollectibleBehaviorCastToolHeadtotals under 100ms. Per call the behavior costs about 30ms, and the pack has 1703 cast-tool stacks (armory and firearms mods add many castable tool variants).Decompiled from 1.9.0-rc.1:
1:Cache bypass for behavior-based workables.
MetalMaterialExtensions.GetOrCacheMetalMaterial(this ItemStack, ...)checkscollectible as IAnvilWorkable. Cast tools carry workability as a behavior, so the collectible itself fails the check and the method falls through to the private uncachedGetMetalMaterial(this CollectibleObject, ...)instead of the cachedGetOrCacheMetalMaterial(this CollectibleObject, ...).2:Null results are never cached.
CacheHelper.GetOrAddreturns early on a null factory result without storing it. Cast tools whose variant is not a resolvable metal material (most modded ones) produce null, so even the cached entry point would recompute them on every call.3:The fallback scan is unindexed. When the direct variant lookup fails,
GetMetalMaterialfalls through toGetSmithingRecipe(linear scan of all smithing recipes) and thenGetGridRecipesAsIngredient(LINQ over every grid recipe times every ingredient, per call). On a large pack the grid scan alone is tens of milliseconds. This is the 30ms.Multiply: 1703 stacks x full registry scan x once per anvil tier x every session x every player.