fix: bag freezing screen on initial open
This commit is contained in:
+79
-10
@@ -22,18 +22,27 @@ function CacheWarmer:WarmBagScanner()
|
||||
end
|
||||
end
|
||||
|
||||
-- Walk player bags 0-4 and queue one ItemDetection:GetItemProperties per
|
||||
-- occupied slot, plus Utils:GetConsumableRestoreTag for consumables. The
|
||||
-- work queue spreads them across frames within the existing 100ms-per-frame
|
||||
-- budget. This warms both tooltip caches the bag-open path reads
|
||||
-- (ItemDetection.detectionCache and Utils.tooltipCache.restoreTag), so the
|
||||
-- first bag open doesn't pay for synchronous tooltip scans.
|
||||
-- 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()
|
||||
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()
|
||||
|
||||
for bagID = 0, 4 do
|
||||
local numSlots = GetContainerNumSlots(bagID)
|
||||
if numSlots and numSlots > 0 then
|
||||
@@ -42,6 +51,7 @@ function CacheWarmer:WarmItemDetectionCache()
|
||||
if link then
|
||||
-- Capture upvalues so the closure has stable bag/slot/link.
|
||||
local b, s, l = bagID, slotID, link
|
||||
|
||||
Utils:QueueWork(function()
|
||||
-- Minimal itemData — GetItemProperties only needs
|
||||
-- .link to compute the cache key; bagID/slotID feed
|
||||
@@ -49,16 +59,57 @@ function CacheWarmer:WarmItemDetectionCache()
|
||||
ItemDetection:GetItemProperties({ link = l }, b, s)
|
||||
end, "CacheWarmer.itemDetection")
|
||||
|
||||
-- Warm the consumable restoreTag cache too. Only consumables
|
||||
-- ever carry the relevant tooltip lines, so check class via
|
||||
-- GetItemInfo (cached by the client after the detection scan
|
||||
-- above) and queue only matching items.
|
||||
-- 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")
|
||||
|
||||
-- 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
|
||||
@@ -71,5 +122,23 @@ function CacheWarmer:Initialize()
|
||||
Guda_ScheduleTimer(0.5, function()
|
||||
CacheWarmer:WarmBagScanner()
|
||||
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
|
||||
|
||||
@@ -1056,6 +1056,17 @@ function CategoryManager:GetCategoriesByPriority()
|
||||
return sorted
|
||||
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
|
||||
-- Returns category ID or "Miscellaneous" as fallback
|
||||
function CategoryManager:CategorizeItem(itemData, bagID, slotID, isOtherChar)
|
||||
|
||||
@@ -581,6 +581,18 @@ function ItemDetection:IsUnusable(itemData, bagID, slotID)
|
||||
return props.isUnusable
|
||||
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)
|
||||
if not bagID or not slotID then return nil end
|
||||
local slotKey = bagID .. ":" .. slotID
|
||||
|
||||
+9
-6
@@ -102,25 +102,28 @@ local function CancelDeferredUsabilityCheck()
|
||||
end
|
||||
end
|
||||
|
||||
-- Update usability tints on all visible item buttons
|
||||
local function UpdateAllUsabilityTints()
|
||||
-- Update usability tints on all visible item buttons — cache-only.
|
||||
-- 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
|
||||
-- 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
|
||||
|
||||
for _, bagParent in pairs(bagParents) do
|
||||
if bagParent and bagParent.itemButtons then
|
||||
for button in pairs(bagParent.itemButtons) do
|
||||
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
|
||||
|
||||
local UpdateAllUsabilityTints = Guda_BagFrame_UpdateAllUsabilityTints
|
||||
|
||||
-- Schedule a deferred usability check with debouncing
|
||||
local function ScheduleDeferredUsabilityCheck()
|
||||
-- Create frame on first use
|
||||
|
||||
+12
-2
@@ -42,9 +42,19 @@ function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems,
|
||||
return
|
||||
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
|
||||
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
|
||||
table.insert(categories[cat], {bagID = bagID, slotID = slotID, itemData = itemData})
|
||||
return
|
||||
|
||||
+17
-5
@@ -848,8 +848,10 @@ end
|
||||
-- The old IsItemUnusable, IsRedColor, and durabilityPattern have been removed
|
||||
-- to prevent redundant tooltip scanning - all detection is now centralized.
|
||||
|
||||
-- Apply/remove red tint on item texture for unusable items
|
||||
local function Guda_ItemButton_UpdateUsableTint(self)
|
||||
-- Apply/remove red tint on item texture for unusable items.
|
||||
-- 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
|
||||
if self.unusableOverlay and self.unusableOverlay.Hide then
|
||||
self.unusableOverlay:Hide()
|
||||
@@ -880,7 +882,16 @@ local function Guda_ItemButton_UpdateUsableTint(self)
|
||||
-- Use cached detection from ItemDetection module (avoids duplicate tooltip scans)
|
||||
local unusable = false
|
||||
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
|
||||
|
||||
-- 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
|
||||
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
|
||||
Guda_ItemButton_UpdateUsableTint(self)
|
||||
Guda_ItemButton_UpdateUsableTint(self, true)
|
||||
end
|
||||
else
|
||||
-- Clear empty slot
|
||||
|
||||
Reference in New Issue
Block a user