Skip to content

Cast tool metal resolution scans the full grid recipe registry per stack; 51s client freeze on first anvil look (large packs) #140

Description

@Lueken

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions