Remove redundant cache warm scans

This commit is contained in:
2026-09-05 15:35:43 +02:00
parent b6aa9ab5a7
commit 8278cbccdd
+63 -93
View File
@@ -1,114 +1,89 @@
-- Guda CacheWarmer
-- Pre-warms ItemDetection's tooltip-scan cache and BagScanner's bag-data
-- cache shortly after PLAYER_LOGIN, in the background, so the FIRST time the
-- user opens the bag frame they don't pay for ~80 synchronous tooltip scans.
-- Pre-warms ItemDetection and category-related tooltip caches shortly after
-- PLAYER_LOGIN so the first bag open does not pay for cold tooltip scans.
--
-- Reuses Utils:QueueWork (Core/Utils.lua) for frame-budgeted background
-- processing — no new scheduler.
-- Guda ClassicAPI uses the low-latency FIFO in Core/Performance.lua. Heavy
-- tooltip operations stay as separate work items so a single callback cannot
-- accidentally bundle several expensive tooltip scans into one frame.
local addon = Guda
local CacheWarmer = {}
addon.Modules.CacheWarmer = CacheWarmer
-- Walk player bags 0-4 and warm BagScanner per-bag cache. Cheap, runs inline.
-- Populate the real BagScanner cache once. The original implementation called
-- ScanBag() for bags 0-4 but discarded every returned table, then GetBagData()
-- immediately scanned the bags again. Returning GetBagData() here makes the
-- warmup useful and gives the later stages the exact cached snapshot.
function CacheWarmer:WarmBagScanner()
local BagScanner = Guda.Modules.BagScanner
if not BagScanner or not BagScanner.ScanBag then return end
-- ScanBag populates the per-bag cache used by GetBagData later.
for bagID = 0, 4 do
local ok = pcall(function() BagScanner:ScanBag(bagID) end)
if not ok then break end
end
if not BagScanner or not BagScanner.GetBagData then return nil end
return BagScanner:GetBagData()
end
-- Walk player bags 0-4 and queue tooltip-scan work for every cache the
-- bag-open path reads, so the first DisplayItemsByCategory never blocks on
-- a cold lookup. Targets:
-- * ItemDetection.detectionCache — used for isJunk / isQuestItem rules
-- * Utils.tooltipCache.restoreTag — used for consumable food/drink grouping
-- * Utils.tooltipCache.bindOnEquip — used for the isBoE rule
-- * CategoryManager.categoryCache — used directly by the layout path
-- Queue order is FIFO and budget-limited to 100ms/frame, so this all runs
-- in the 0.5s–~2s window after PLAYER_LOGIN.
function CacheWarmer:WarmItemDetectionCache()
-- Queue tooltip/cache work using the already-cached BagScanner snapshot.
-- This avoids another round of GetContainerNumSlots/GetContainerItemLink and
-- avoids GetItemInfo calls just to discover the item class.
function CacheWarmer:WarmItemDetectionCache(bagData)
local Utils = Guda.Modules.Utils
local ItemDetection = Guda.Modules.ItemDetection
local CategoryManager = Guda.Modules.CategoryManager
local BagScanner = Guda.Modules.BagScanner
if not (Utils and Utils.QueueWork) then return end
if not (ItemDetection and ItemDetection.GetItemProperties) then return end
-- WarmBagScanner ran just before us, so GetBagData() is a cache hit and
-- returns the already-populated itemData with class/subclass/quality.
local bagData = BagScanner and BagScanner.GetBagData and BagScanner:GetBagData()
bagData = bagData or (BagScanner and BagScanner.GetBagData
and BagScanner:GetBagData())
if not bagData then return end
for bagID = 0, 4 do
local numSlots = GetContainerNumSlots(bagID)
if numSlots and numSlots > 0 then
for slotID = 1, numSlots do
local link = GetContainerItemLink(bagID, slotID)
if link then
-- Capture upvalues so the closure has stable bag/slot/link.
local b, s, l = bagID, slotID, link
local bag = bagData[bagID]
if bag and bag.slots then
for slotID, slotData in pairs(bag.slots) do
if slotData and slotData.link then
local b, s = bagID, slotID
local snapshot = {
link = slotData.link,
itemID = slotData.itemID,
name = slotData.name,
class = slotData.class,
type = slotData.type,
subclass = slotData.subclass,
quality = slotData.quality,
texture = slotData.texture,
equipSlot = slotData.equipSlot,
}
-- Central property scan. Keep this as its own queue item:
-- SetBagItem can be expensive on a cold client cache.
Utils:QueueWork(function()
-- Minimal itemData — GetItemProperties only needs
-- .link to compute the cache key; bagID/slotID feed
-- the tooltip scan.
ItemDetection:GetItemProperties({ link = l }, b, s)
ItemDetection:GetItemProperties(snapshot, b, s)
end, "CacheWarmer.itemDetection")
-- Consumable restoreTag cache (only consumables carry
-- "while eating" / "while drinking" / "use: restores").
Utils:QueueWork(function()
local _, _, _, _, itemType = GetItemInfo(l)
if itemType == "Consumable" and Utils.GetConsumableRestoreTag then
Utils:GetConsumableRestoreTag(b, s, l)
end
end, "CacheWarmer.restoreTag")
-- Only consumables need the restore-text tooltip cache.
if snapshot.class == "Consumable"
and Utils.GetConsumableRestoreTag then
Utils:QueueWork(function()
snapshot.restoreTag =
Utils:GetConsumableRestoreTag(b, s, snapshot.link)
end, "CacheWarmer.restoreTag")
end
-- BoE tooltip cache. Only weapons/armor can be BoE; skip
-- everything else so we don't scan consumables / trade
-- goods tooltips for nothing.
Utils:QueueWork(function()
local _, _, _, _, _, class = GetItemInfo(l)
if (class == "Weapon" or class == "Armor") and Utils.IsBindOnEquip then
Utils:IsBindOnEquip(b, s, l)
end
end, "CacheWarmer.boe")
-- Only equipment can be Bind on Equip.
if (snapshot.class == "Weapon" or snapshot.class == "Armor")
and Utils.IsBindOnEquip then
Utils:QueueWork(function()
Utils:IsBindOnEquip(b, s, snapshot.link)
end, "CacheWarmer.boe")
end
-- CategoryManager cache. Snapshot itemData fields because
-- BagScanner uses a pool and may recycle the table later.
-- The restoreTag field is populated inside the callback
-- (not here) because the restoreTag warmer runs first via
-- FIFO queue order — so by the time this fires, the
-- tooltipCache.restoreTag lookup is a cache hit.
if CategoryManager and CategoryManager.CategorizeItem and bagData then
local bag = bagData[b]
local slotData = bag and bag.slots and bag.slots[s]
if slotData and slotData.link then
local snapshot = {
link = slotData.link,
name = slotData.name,
class = slotData.class,
type = slotData.type,
subclass = slotData.subclass,
quality = slotData.quality,
texture = slotData.texture,
equipSlot = slotData.equipSlot,
}
Utils:QueueWork(function()
-- Pull restoreTag from warm cache so rules that
-- key on it (Food/Drink categories) match.
if snapshot.class == "Consumable"
and Utils.GetConsumableRestoreTag then
snapshot.restoreTag =
Utils:GetConsumableRestoreTag(b, s, snapshot.link)
end
CategoryManager:CategorizeItem(snapshot, b, s, false)
end, "CacheWarmer.category")
end
-- Runs after the relevant warmers because the queue is FIFO,
-- so category rules hit warm ItemDetection/tooltip caches.
if CategoryManager and CategoryManager.CategorizeItem then
Utils:QueueWork(function()
CategoryManager:CategorizeItem(snapshot, b, s, false)
end, "CacheWarmer.category")
end
end
end
@@ -117,17 +92,12 @@ function CacheWarmer:WarmItemDetectionCache()
end
function CacheWarmer:Initialize()
-- Defer ~0.5s so we don't compete with the busy login frame. The user
-- typically opens bags several seconds later, so the cache will be hot.
-- Defer startup work so we do not compete with the login frame.
Guda_ScheduleTimer(0.5, function()
CacheWarmer:WarmBagScanner()
CacheWarmer:WarmItemDetectionCache()
local bagData = CacheWarmer:WarmBagScanner()
CacheWarmer:WarmItemDetectionCache(bagData)
-- Completion marker — runs after all prior warmers drain. Re-layouts
-- the bag so items that fell back to their class bucket on the first
-- (cold-cache) render now land in their real categories, and re-sweeps
-- tints for items whose detection finished while the bag was already
-- open.
-- Completion marker runs after all prior FIFO work drains.
local Utils = Guda.Modules.Utils
if Utils and Utils.QueueWork then
Utils:QueueWork(function()