Skip to content

Commit cbd668f

Browse files
committed
Unify radius jewel tooltip comparison caching
Use the existing output cache for slot-only and multi-slot hovers instead of retaining temporary passive specs. Keep calculation-only specs free of UI path data while preserving socket distances. This reduces duplicate state and repeated calculations without changing tooltip content.
1 parent cfaf06f commit cbd668f

3 files changed

Lines changed: 77 additions & 58 deletions

File tree

‎spec/System/TestRadiusJewelStatDiff_spec.lua‎

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ describe("TestRadiusJewelStatDiff", function()
668668
"limited unique radius jewels should rebuild only the same-unique slot that will be displayed")
669669
end)
670670

671-
it("AddItemTooltip reuses slot-only radius jewel comparison specs until output changes", function()
671+
it("AddItemTooltip reuses slot-only radius jewel comparison outputs until output changes", function()
672672
local spec, sockets = setupAllocatedSockets(2)
673673

674674
local item = newCustomLeapJewel("Cached Leap")
@@ -678,29 +678,50 @@ describe("TestRadiusJewelStatDiff", function()
678678

679679
local originalSlotOnlyTooltips = main.slotOnlyTooltips
680680
main.slotOnlyTooltips = true
681+
build.itemsTab.jewelComparisonOutputCache = nil
681682
local specClass = getmetatable(spec)
682683
local originalBuildAllDependsAndPaths = specClass.BuildAllDependsAndPaths
683684
local rebuilds = 0
684685
specClass.BuildAllDependsAndPaths = function(self, ...)
685686
rebuilds = rebuilds + 1
686687
return originalBuildAllDependsAndPaths(self, ...)
687688
end
689+
local originalGetMiscCalculator = build.calcsTab.GetMiscCalculator
690+
local calcCalls = 0
691+
build.calcsTab.GetMiscCalculator = function(self, ...)
692+
local calcFunc, calcBase = originalGetMiscCalculator(self, ...)
693+
return function(...)
694+
calcCalls = calcCalls + 1
695+
return calcFunc(...)
696+
end, calcBase
697+
end
688698

689699
local ok, err = pcall(function()
690700
local tooltip = new("Tooltip"):Tooltip()
691701
build.itemsTab:AddItemTooltip(tooltip, item, slot)
702+
local firstPassTooltipText = tooltipText(tooltip)
703+
local firstPassCalcCalls = calcCalls
704+
assert.is_true(firstPassCalcCalls > 0,
705+
"slot-only radius jewel hover should calculate its output on first pass")
692706
tooltip = new("Tooltip"):Tooltip()
693707
build.itemsTab:AddItemTooltip(tooltip, item, slot)
708+
assert.are.equals(firstPassTooltipText, tooltipText(tooltip),
709+
"cached slot-only radius output should preserve tooltip content")
694710
assert.are.equals(1, rebuilds,
695-
"slot-only radius jewel hover should reuse its cached comparison spec")
711+
"slot-only radius jewel hover should reuse its cached comparison output")
712+
assert.are.equals(firstPassCalcCalls, calcCalls,
713+
"slot-only radius jewel hover should not recalculate a cached output")
696714

697715
build.outputRevision = build.outputRevision + 1
698716
tooltip = new("Tooltip"):Tooltip()
699717
build.itemsTab:AddItemTooltip(tooltip, item, slot)
700718
assert.are.equals(2, rebuilds,
701-
"slot-only radius jewel comparison spec cache should reset when output changes")
719+
"slot-only radius jewel output cache should reset when output changes")
720+
assert.is_true(calcCalls > firstPassCalcCalls,
721+
"slot-only radius jewel comparison should recalculate after output changes")
702722
end)
703723
specClass.BuildAllDependsAndPaths = originalBuildAllDependsAndPaths
724+
build.calcsTab.GetMiscCalculator = originalGetMiscCalculator
704725
main.slotOnlyTooltips = originalSlotOnlyTooltips
705726
if not ok then
706727
error(err)
@@ -723,10 +744,13 @@ describe("TestRadiusJewelStatDiff", function()
723744
local originalSlotOnlyTooltips = main.slotOnlyTooltips
724745
main.slotOnlyTooltips = true
725746
local specClass = getmetatable(spec)
747+
local originalBuildAllDependsAndPaths = specClass.BuildAllDependsAndPaths
726748
local originalBuildPathFromNode = specClass.BuildPathFromNode
727749
local originalSetNodeDistanceToClassStart = specClass.SetNodeDistanceToClassStart
750+
local originalBuildSplitPersonalityPath = specClass.BuildSplitPersonalityPath
728751
local buildPathCalls = 0
729752
local distanceCalls = 0
753+
local splitPersonalityPathCalls = 0
730754
specClass.BuildPathFromNode = function(self, ...)
731755
buildPathCalls = buildPathCalls + 1
732756
return originalBuildPathFromNode(self, ...)
@@ -735,22 +759,42 @@ describe("TestRadiusJewelStatDiff", function()
735759
distanceCalls = distanceCalls + 1
736760
return originalSetNodeDistanceToClassStart(self, ...)
737761
end
762+
specClass.BuildSplitPersonalityPath = function(self, ...)
763+
splitPersonalityPathCalls = splitPersonalityPathCalls + 1
764+
return originalBuildSplitPersonalityPath(self, ...)
765+
end
738766

739767
local ok, err = pcall(function()
740768
local tooltip = new("Tooltip"):Tooltip()
741769
build.itemsTab:AddItemTooltip(tooltip, radiusItem, radiusSlot)
770+
local calculationOnlyTooltipText = tooltipText(tooltip)
771+
assert.are.equals(0, buildPathCalls,
772+
"calculation-only tooltip specs should not rebuild UI node paths")
773+
assert.is_true(distanceCalls > 0,
774+
"calculation-only tooltip specs should refresh jewel socket distances used by calc")
775+
assert.are.equals(0, splitPersonalityPathCalls,
776+
"calculation-only tooltip specs should not rebuild Split Personality highlight paths")
777+
778+
build.itemsTab.jewelComparisonOutputCache = nil
779+
specClass.BuildAllDependsAndPaths = function(self)
780+
return originalBuildAllDependsAndPaths(self)
781+
end
782+
local fullRebuildTooltip = new("Tooltip"):Tooltip()
783+
build.itemsTab:AddItemTooltip(fullRebuildTooltip, radiusItem, radiusSlot)
784+
assert.are.equals(calculationOnlyTooltipText, tooltipText(fullRebuildTooltip),
785+
"calculation-only radius jewel specs should preserve full-rebuild tooltip output")
786+
assert.is_true(splitPersonalityPathCalls > 0,
787+
"the full-rebuild comparison should exercise Split Personality highlight paths")
742788
end)
789+
specClass.BuildAllDependsAndPaths = originalBuildAllDependsAndPaths
743790
specClass.BuildPathFromNode = originalBuildPathFromNode
744791
specClass.SetNodeDistanceToClassStart = originalSetNodeDistanceToClassStart
792+
specClass.BuildSplitPersonalityPath = originalBuildSplitPersonalityPath
745793
main.slotOnlyTooltips = originalSlotOnlyTooltips
746794
if not ok then
747795
error(err)
748796
end
749797

750-
assert.are.equals(0, buildPathCalls,
751-
"temporary tooltip specs should not rebuild UI node paths")
752-
assert.is_true(distanceCalls > 0,
753-
"temporary tooltip specs should still refresh jewel socket distances used by calc")
754798
end)
755799

756800
it("AddItemTooltip reuses full radius jewel comparison outputs until output changes", function()
@@ -764,7 +808,6 @@ describe("TestRadiusJewelStatDiff", function()
764808
local originalSlotOnlyTooltips = main.slotOnlyTooltips
765809
main.slotOnlyTooltips = false
766810
build.itemsTab.jewelComparisonOutputCache = nil
767-
build.itemsTab.slotOnlyJewelComparisonSpecCache = nil
768811

769812
local originalGetMiscCalculator = build.calcsTab.GetMiscCalculator
770813
local calcCalls = 0

‎src/Classes/ItemsTab.lua‎

Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4341,33 +4341,12 @@ local function cloneSpecForJewelComparison(spec)
43414341
return specCopy
43424342
end
43434343

4344-
local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementItem, useJewelComparisonSpecCache)
4344+
local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementItem)
43454345
local tempItemId
4346-
local replacementItemId = replacementItem and replacementItem.id
4347-
local replacementItemIsStored = replacementItemId and itemsTab.items[replacementItemId] == replacementItem
4348-
local canCache = useJewelComparisonSpecCache and (not replacementItem or replacementItemIsStored)
4349-
local cacheKey
4350-
local cache
4351-
if canCache then
4352-
local outputRevision = itemsTab.build and itemsTab.build.outputRevision or 0
4353-
cache = itemsTab.slotOnlyJewelComparisonSpecCache
4354-
if not cache or cache.outputRevision ~= outputRevision then
4355-
cache = {
4356-
outputRevision = outputRevision,
4357-
specs = { },
4358-
}
4359-
itemsTab.slotOnlyJewelComparisonSpecCache = cache
4360-
end
4361-
cacheKey = tostring(compareSlot.nodeId) .. ":" .. tostring(replacementItemId or "")
4362-
if cache.specs[cacheKey] then
4363-
return cache.specs[cacheKey]
4364-
end
4365-
end
4366-
43674346
local spec = cloneSpecForJewelComparison(itemsTab.build.spec)
43684347
if replacementItem then
4369-
if replacementItemIsStored then
4370-
spec.jewels[compareSlot.nodeId] = replacementItemId
4348+
if replacementItem.id and itemsTab.items[replacementItem.id] == replacementItem then
4349+
spec.jewels[compareSlot.nodeId] = replacementItem.id
43714350
else
43724351
tempItemId = -1
43734352
while itemsTab.items[tempItemId] do
@@ -4381,9 +4360,9 @@ local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementIte
43814360
end
43824361

43834362
local ok, err = xpcall(function()
4384-
-- These temporary specs only feed the misc calculator, which does not read node.path/pathDist.
4385-
-- Jewel socket distances are still rebuilt for jewel scaling;
4386-
-- Split Personality highlight paths are also refreshed.
4363+
-- These temporary specs only feed the misc calculator, which does not read regular
4364+
-- node paths or Split Personality highlight paths. Jewel socket distances are still
4365+
-- rebuilt for Split Personality modifier scaling.
43874366
spec:BuildAllDependsAndPaths(true)
43884367
end, debug.traceback)
43894368
if tempItemId then
@@ -4392,9 +4371,6 @@ local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementIte
43924371
if not ok then
43934372
error(err, 0)
43944373
end
4395-
if cacheKey then
4396-
cache.specs[cacheKey] = spec
4397-
end
43984374
return spec
43994375
end
44004376

@@ -5118,32 +5094,30 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth)
51185094

51195095
tooltip:AddLine(14, colorCodes.TIP .. "Tip: Press Ctrl+D"..itemTabHint.." to disable the display of stat differences.")
51205096

5121-
local function getReplacedItemAndOutput(compareSlot, selItem, useJewelComparisonSpecCache, useJewelComparisonOutputCache)
5097+
local function getReplacedItemAndOutput(compareSlot, selItem)
51225098
selItem = selItem or self.items[compareSlot.selItemId]
51235099
local override = { repSlotName = compareSlot.slotName, repItem = item ~= selItem and item or nil }
51245100
local outputCache
51255101
local outputCacheKey
51265102
if compareSlot.nodeId and (itemChangesPassiveTree(selItem) or itemChangesPassiveTree(item)) then
5127-
if useJewelComparisonOutputCache then
5128-
outputCacheKey = getJewelComparisonOutputCacheKey(self, compareSlot, override.repItem)
5129-
if outputCacheKey then
5130-
outputCache = getJewelComparisonOutputCache(self)
5131-
if outputCache.outputs[outputCacheKey] then
5132-
return selItem, outputCache.outputs[outputCacheKey]
5133-
end
5103+
outputCacheKey = getJewelComparisonOutputCacheKey(self, compareSlot, override.repItem)
5104+
if outputCacheKey then
5105+
outputCache = getJewelComparisonOutputCache(self)
5106+
if outputCache.outputs[outputCacheKey] then
5107+
return selItem, outputCache.outputs[outputCacheKey]
51345108
end
51355109
end
5136-
override.spec = buildSpecForJewelComparison(self, compareSlot, override.repItem, useJewelComparisonSpecCache)
5110+
override.spec = buildSpecForJewelComparison(self, compareSlot, override.repItem)
51375111
end
51385112
local output = calcFunc(override)
51395113
if outputCacheKey then
51405114
outputCache.outputs[outputCacheKey] = output
51415115
end
51425116
return selItem, output
51435117
end
5144-
local function addCompareForSlot(compareSlot, selItem, output, useJewelComparisonSpecCache)
5118+
local function addCompareForSlot(compareSlot, selItem, output)
51455119
if not selItem or not output then
5146-
selItem, output = getReplacedItemAndOutput(compareSlot, nil, useJewelComparisonSpecCache)
5120+
selItem, output = getReplacedItemAndOutput(compareSlot)
51475121
end
51485122
local header
51495123
if item == selItem then
@@ -5159,7 +5133,7 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth)
51595133
-- one slot.
51605134
local compareOnlySlot = type(slot) ~= "string" and slot or self.slots[slot]
51615135
if main.slotOnlyTooltips and slot then
5162-
if compareOnlySlot then addCompareForSlot(compareOnlySlot, nil, nil, true) end
5136+
if compareOnlySlot then addCompareForSlot(compareOnlySlot) end
51635137
return
51645138
end
51655139

@@ -5180,7 +5154,7 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth)
51805154
local slots = {}
51815155
for _, slotEntry in ipairs(slotCandidates) do
51825156
if not isLimitedUniqueAtLimit or slotEntry.isSameUnique then
5183-
local _, output = getReplacedItemAndOutput(slotEntry.compareSlot, slotEntry.selItem, nil, true)
5157+
local _, output = getReplacedItemAndOutput(slotEntry.compareSlot, slotEntry.selItem)
51845158
slotEntry.output = output
51855159
table.insert(slots, slotEntry)
51865160
end

‎src/Classes/PassiveSpec.lua‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,10 +1099,10 @@ function PassiveSpecClass:NodesInIntuitiveLeapLikeRadius(node)
10991099
end
11001100

11011101
-- Rebuilds dependencies and calculation distances for all nodes.
1102-
-- When node paths are skipped, node.path/pathDist remain unset while jewel socket
1103-
-- distanceToClassStart values and Split Personality paths are still refreshed.
1104-
---@param skipNodePathRebuild? boolean
1105-
function PassiveSpecClass:BuildAllDependsAndPaths(skipNodePathRebuild)
1102+
-- Calculation-only specs leave UI path fields unset while still refreshing jewel
1103+
-- socket distanceToClassStart values used by the calculator.
1104+
---@param calculationOnly? boolean
1105+
function PassiveSpecClass:BuildAllDependsAndPaths(calculationOnly)
11061106
local timelessJewelTypeByConqueror = {
11071107
vaal = 1,
11081108
karui = 2,
@@ -1600,7 +1600,7 @@ function PassiveSpecClass:BuildAllDependsAndPaths(skipNodePathRebuild)
16001600

16011601
-- Reset and rebuild all node paths
16021602
for id, node in pairs(self.nodes) do
1603-
if skipNodePathRebuild then
1603+
if calculationOnly then
16041604
node.pathDist = nil
16051605
node.path = nil
16061606
else
@@ -1613,7 +1613,7 @@ function PassiveSpecClass:BuildAllDependsAndPaths(skipNodePathRebuild)
16131613
end
16141614
for id, node in pairs(self.allocNodes) do
16151615
if #node.intuitiveLeapLikesAffecting == 0 or node.connectedToStart then
1616-
if not skipNodePathRebuild then
1616+
if not calculationOnly then
16171617
self:BuildPathFromNode(node)
16181618
end
16191619
if node.isJewelSocket or node.expansionJewel then
@@ -1622,7 +1622,9 @@ function PassiveSpecClass:BuildAllDependsAndPaths(skipNodePathRebuild)
16221622
end
16231623
end
16241624

1625-
self:BuildSplitPersonalityPath()
1625+
if not calculationOnly then
1626+
self:BuildSplitPersonalityPath()
1627+
end
16261628
end
16271629

16281630
function PassiveSpecClass:ReplaceNode(old, newNode)

0 commit comments

Comments
 (0)