fix: bag freezing screen on initial open

This commit is contained in:
Vati
2026-04-16 14:00:41 +04:00
parent 9f3a6275ac
commit f1618b71bd
6 changed files with 140 additions and 23 deletions
+79 -10
View File
@@ -22,18 +22,27 @@ function CacheWarmer:WarmBagScanner()
end end
end end
-- Walk player bags 0-4 and queue one ItemDetection:GetItemProperties per -- Walk player bags 0-4 and queue tooltip-scan work for every cache the
-- occupied slot, plus Utils:GetConsumableRestoreTag for consumables. The -- bag-open path reads, so the first DisplayItemsByCategory never blocks on
-- work queue spreads them across frames within the existing 100ms-per-frame -- a cold lookup. Targets:
-- budget. This warms both tooltip caches the bag-open path reads -- * ItemDetection.detectionCache — used for isJunk / isQuestItem rules
-- (ItemDetection.detectionCache and Utils.tooltipCache.restoreTag), so the -- * Utils.tooltipCache.restoreTag — used for consumable food/drink grouping
-- first bag open doesn't pay for synchronous tooltip scans. -- * 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() function CacheWarmer:WarmItemDetectionCache()
local Utils = Guda.Modules.Utils local Utils = Guda.Modules.Utils
local ItemDetection = Guda.Modules.ItemDetection 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 (Utils and Utils.QueueWork) then return end
if not (ItemDetection and ItemDetection.GetItemProperties) 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()
for bagID = 0, 4 do for bagID = 0, 4 do
local numSlots = GetContainerNumSlots(bagID) local numSlots = GetContainerNumSlots(bagID)
if numSlots and numSlots > 0 then if numSlots and numSlots > 0 then
@@ -42,6 +51,7 @@ function CacheWarmer:WarmItemDetectionCache()
if link then if link then
-- Capture upvalues so the closure has stable bag/slot/link. -- Capture upvalues so the closure has stable bag/slot/link.
local b, s, l = bagID, slotID, link local b, s, l = bagID, slotID, link
Utils:QueueWork(function() Utils:QueueWork(function()
-- Minimal itemData — GetItemProperties only needs -- Minimal itemData — GetItemProperties only needs
-- .link to compute the cache key; bagID/slotID feed -- .link to compute the cache key; bagID/slotID feed
@@ -49,16 +59,57 @@ function CacheWarmer:WarmItemDetectionCache()
ItemDetection:GetItemProperties({ link = l }, b, s) ItemDetection:GetItemProperties({ link = l }, b, s)
end, "CacheWarmer.itemDetection") end, "CacheWarmer.itemDetection")
-- Warm the consumable restoreTag cache too. Only consumables -- Consumable restoreTag cache (only consumables carry
-- ever carry the relevant tooltip lines, so check class via -- "while eating" / "while drinking" / "use: restores").
-- GetItemInfo (cached by the client after the detection scan
-- above) and queue only matching items.
Utils:QueueWork(function() Utils:QueueWork(function()
local _, _, _, _, itemType = GetItemInfo(l) local _, _, _, _, itemType = GetItemInfo(l)
if itemType == "Consumable" and Utils.GetConsumableRestoreTag then if itemType == "Consumable" and Utils.GetConsumableRestoreTag then
Utils:GetConsumableRestoreTag(b, s, l) Utils:GetConsumableRestoreTag(b, s, l)
end end
end, "CacheWarmer.restoreTag") end, "CacheWarmer.restoreTag")
-- 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")
-- 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
end
end end
end end
end end
@@ -71,5 +122,23 @@ function CacheWarmer:Initialize()
Guda_ScheduleTimer(0.5, function() Guda_ScheduleTimer(0.5, function()
CacheWarmer:WarmBagScanner() CacheWarmer:WarmBagScanner()
CacheWarmer:WarmItemDetectionCache() CacheWarmer:WarmItemDetectionCache()
-- 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.
local Utils = Guda.Modules.Utils
if Utils and Utils.QueueWork then
Utils:QueueWork(function()
if Guda_BagFrame and Guda_BagFrame:IsShown()
and Guda.Modules.BagFrame and Guda.Modules.BagFrame.Update then
Guda.Modules.BagFrame:Update()
end
if Guda_BagFrame_UpdateAllUsabilityTints then
Guda_BagFrame_UpdateAllUsabilityTints()
end
end, "CacheWarmer.completionSweep")
end
end) end)
end end
+11
View File
@@ -1056,6 +1056,17 @@ function CategoryManager:GetCategoriesByPriority()
return sorted return sorted
end end
-- Cache-only variant. Returns the cached category id if present, or nil.
-- Used by the bag-open layout path so it never triggers cold-cache rule
-- evaluation (which can cascade into tooltip scans per item). CacheWarmer
-- populates the cache in the background; bags re-layout once warmup finishes.
function CategoryManager:CategorizeItemCached(itemData, isOtherChar)
if not itemData or not itemData.link then return nil end
local cacheKey = GetCacheKey(itemData.link, isOtherChar)
if not cacheKey then return nil end
return categoryCache[cacheKey]
end
-- Categorize an item using the rule engine -- Categorize an item using the rule engine
-- Returns category ID or "Miscellaneous" as fallback -- Returns category ID or "Miscellaneous" as fallback
function CategoryManager:CategorizeItem(itemData, bagID, slotID, isOtherChar) function CategoryManager:CategorizeItem(itemData, bagID, slotID, isOtherChar)
+12
View File
@@ -581,6 +581,18 @@ function ItemDetection:IsUnusable(itemData, bagID, slotID)
return props.isUnusable return props.isUnusable
end end
-- Cache-only lookup. Returns true/false when cached, nil when unknown
-- (no tooltip scan, no side effects). Lets the layout path skip tinting
-- on cold cache without blocking on ~80 tooltip scans.
function ItemDetection:IsUnusableCached(itemData)
if not itemData or not itemData.link then return nil end
local cacheKey = GetCacheKey(itemData.link)
if not cacheKey then return nil end
local props = detectionCache[cacheKey]
if not props then return nil end
return props.isUnusable
end
function ItemDetection:GetCharges(itemData, bagID, slotID) function ItemDetection:GetCharges(itemData, bagID, slotID)
if not bagID or not slotID then return nil end if not bagID or not slotID then return nil end
local slotKey = bagID .. ":" .. slotID local slotKey = bagID .. ":" .. slotID
+9 -6
View File
@@ -102,25 +102,28 @@ local function CancelDeferredUsabilityCheck()
end end
end end
-- Update usability tints on all visible item buttons -- Update usability tints on all visible item buttons — cache-only.
local function UpdateAllUsabilityTints() -- Every SetBagItem tooltip scan on a cold item can block 50-500ms, so we
-- never trigger scans from the bag-open path. Tints appear for items
-- already cached; uncached items stay untinted until CacheWarmer fills
-- them in (its BagFrame hook triggers a re-sweep when done).
function Guda_BagFrame_UpdateAllUsabilityTints()
if not Guda_BagFrame or not Guda_BagFrame:IsShown() then return end if not Guda_BagFrame or not Guda_BagFrame:IsShown() then return end
-- Don't scan ~80 tooltips synchronously while the user is dragging the
-- frame — it can stall the engine for multiple seconds on cold caches.
-- The caller will reschedule after the drag ends.
if isFrameMoving then return end if isFrameMoving then return end
for _, bagParent in pairs(bagParents) do for _, bagParent in pairs(bagParents) do
if bagParent and bagParent.itemButtons then if bagParent and bagParent.itemButtons then
for button in pairs(bagParent.itemButtons) do for button in pairs(bagParent.itemButtons) do
if button.hasItem and button:IsShown() and Guda_ItemButton_UpdateUsableTint then if button.hasItem and button:IsShown() and Guda_ItemButton_UpdateUsableTint then
Guda_ItemButton_UpdateUsableTint(button) Guda_ItemButton_UpdateUsableTint(button, true) -- cacheOnly
end end
end end
end end
end end
end end
local UpdateAllUsabilityTints = Guda_BagFrame_UpdateAllUsabilityTints
-- Schedule a deferred usability check with debouncing -- Schedule a deferred usability check with debouncing
local function ScheduleDeferredUsabilityCheck() local function ScheduleDeferredUsabilityCheck()
-- Create frame on first use -- Create frame on first use
+12 -2
View File
@@ -42,9 +42,19 @@ function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems,
return return
end end
-- Use CategoryManager rule engine if available, otherwise fall back to legacy logic -- Use CategoryManager rule engine if available, otherwise fall back to legacy logic.
-- Fast path: cache-only lookup. CacheWarmer populates categoryCache in the
-- background; on cold miss we skip rule evaluation (which would otherwise
-- trigger tooltip scans per item) and use itemData.class as a rough bucket.
-- CacheWarmer's completion marker re-triggers BagFrame:Update so items land
-- in their real categories once warmup finishes.
if addon.Modules.CategoryManager then if addon.Modules.CategoryManager then
cat = addon.Modules.CategoryManager:CategorizeItem(itemData, bagID, slotID, isOtherChar) if addon.Modules.CategoryManager.CategorizeItemCached then
cat = addon.Modules.CategoryManager:CategorizeItemCached(itemData, isOtherChar)
end
if not cat then
cat = itemData.class or "Miscellaneous"
end
if not categories[cat] then cat = "Miscellaneous" end if not categories[cat] then cat = "Miscellaneous" end
table.insert(categories[cat], {bagID = bagID, slotID = slotID, itemData = itemData}) table.insert(categories[cat], {bagID = bagID, slotID = slotID, itemData = itemData})
return return
+17 -5
View File
@@ -848,8 +848,10 @@ end
-- The old IsItemUnusable, IsRedColor, and durabilityPattern have been removed -- The old IsItemUnusable, IsRedColor, and durabilityPattern have been removed
-- to prevent redundant tooltip scanning - all detection is now centralized. -- to prevent redundant tooltip scanning - all detection is now centralized.
-- Apply/remove red tint on item texture for unusable items -- Apply/remove red tint on item texture for unusable items.
local function Guda_ItemButton_UpdateUsableTint(self) -- When cacheOnly is true, skips the tooltip scan on cold cache and returns
-- untinted — the deferred pass in BagFrame fills in tints later.
local function Guda_ItemButton_UpdateUsableTint(self, cacheOnly)
-- Clear any existing tint/overlay first -- Clear any existing tint/overlay first
if self.unusableOverlay and self.unusableOverlay.Hide then if self.unusableOverlay and self.unusableOverlay.Hide then
self.unusableOverlay:Hide() self.unusableOverlay:Hide()
@@ -880,7 +882,16 @@ local function Guda_ItemButton_UpdateUsableTint(self)
-- Use cached detection from ItemDetection module (avoids duplicate tooltip scans) -- Use cached detection from ItemDetection module (avoids duplicate tooltip scans)
local unusable = false local unusable = false
if self.itemData and addon.Modules.ItemDetection then if self.itemData and addon.Modules.ItemDetection then
unusable = addon.Modules.ItemDetection:IsUnusable(self.itemData, self.bagID, self.slotID) if cacheOnly then
local cached = addon.Modules.ItemDetection:IsUnusableCached(self.itemData)
if cached == nil then
-- Unknown; leave untinted. Deferred pass fills this in later.
return
end
unusable = cached
else
unusable = addon.Modules.ItemDetection:IsUnusable(self.itemData, self.bagID, self.slotID)
end
end end
-- Ensure overlay exists (created in OnLoad, but be defensive) -- Ensure overlay exists (created in OnLoad, but be defensive)
@@ -1885,9 +1896,10 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
if cd and cd.Hide then cd:Hide() end if cd and cd.Hide then cd:Hide() end
end end
-- Update unusable red overlay tint -- Update unusable red overlay tint (cache-only on the layout path;
-- deferred pass in BagFrame runs the full scan spread via QueueWork).
if Guda_ItemButton_UpdateUsableTint then if Guda_ItemButton_UpdateUsableTint then
Guda_ItemButton_UpdateUsableTint(self) Guda_ItemButton_UpdateUsableTint(self, true)
end end
else else
-- Clear empty slot -- Clear empty slot