From 5943d28887f4b91f249321ddceb119661baf4a8f Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sun, 4 Jan 2026 17:14:08 +0400 Subject: [PATCH] performance: debouncing added to some operations --- Core/Tooltip.lua | 17 +++++- UI/BagFrame.lua | 52 +++++++++++++++--- UI/BankFrame.lua | 51 +++++++++++++++--- UI/QuestItemBar.lua | 123 +++++++++++++++++++++--------------------- UI/TrackedItemBar.lua | 17 +++++- 5 files changed, 179 insertions(+), 81 deletions(-) diff --git a/Core/Tooltip.lua b/Core/Tooltip.lua index ba118f5..01285ac 100644 --- a/Core/Tooltip.lua +++ b/Core/Tooltip.lua @@ -647,12 +647,25 @@ function Tooltip:Initialize() addon:Debug("Tooltip cache cleared") end - -- Clear cache on bag updates + -- Clear cache on bag updates (debounced to prevent lag on rapid updates) local frame = CreateFrame("Frame") + local cacheClearPending = false frame:RegisterEvent("BAG_UPDATE") frame:SetScript("OnEvent", function() if event == "BAG_UPDATE" then - Tooltip:ClearCache() + if cacheClearPending then return end + cacheClearPending = true + -- Debounce: batch rapid BAG_UPDATE events + local debounceFrame = CreateFrame("Frame") + debounceFrame.elapsed = 0 + debounceFrame:SetScript("OnUpdate", function() + this.elapsed = this.elapsed + arg1 + if this.elapsed >= 0.2 then + this:SetScript("OnUpdate", nil) + cacheClearPending = false + Tooltip:ClearCache() + end + end) end end) diff --git a/UI/BagFrame.lua b/UI/BagFrame.lua index 99cb8e0..c56e563 100644 --- a/UI/BagFrame.lua +++ b/UI/BagFrame.lua @@ -2235,11 +2235,33 @@ function BagFrame:Initialize() end end) - -- Update on bag changes + -- Debounce state for BagFrame updates (prevents lag from rapid events) + local bagUpdatePending = false + local lockUpdatePending = false + + -- Helper to schedule a debounced BagFrame update + local function ScheduleBagFrameUpdate(delay) + if bagUpdatePending then return end + bagUpdatePending = true + local debounceFrame = CreateFrame("Frame") + debounceFrame.elapsed = 0 + debounceFrame:SetScript("OnUpdate", function() + this.elapsed = this.elapsed + arg1 + if this.elapsed >= delay then + this:SetScript("OnUpdate", nil) + bagUpdatePending = false + if not currentViewChar and Guda_BagFrame:IsShown() then + BagFrame:Update() + end + end + end) + end + + -- Update on bag changes (debounced to prevent lag on rapid bag updates) addon.Modules.Events:OnBagUpdate(function() - if not currentViewChar then - BagFrame:Update() - end + if currentViewChar then return end + if not Guda_BagFrame:IsShown() then return end + ScheduleBagFrameUpdate(0.1) end, "BagFrame") -- Update item cooldown overlays when item cooldowns change @@ -2256,11 +2278,25 @@ function BagFrame:Initialize() BagFrame:UpdateMoney() end, "BagFrame") - -- Update when items get locked/unlocked (for trading, mailing, etc.) + -- Update when items get locked/unlocked (debounced for trading, mailing, etc.) addon.Modules.Events:Register("ITEM_LOCK_CHANGED", function() - if not currentViewChar then - BagFrame:Update() - end + if currentViewChar then return end + if not Guda_BagFrame:IsShown() then return end + -- Use slightly longer debounce for lock changes (they fire rapidly during drags) + if lockUpdatePending then return end + lockUpdatePending = true + local debounceFrame = CreateFrame("Frame") + debounceFrame.elapsed = 0 + debounceFrame:SetScript("OnUpdate", function() + this.elapsed = this.elapsed + arg1 + if this.elapsed >= 0.15 then + this:SetScript("OnUpdate", nil) + lockUpdatePending = false + if Guda_BagFrame:IsShown() then + BagFrame:Update() + end + end + end) end, "BagFrame") -- Auto-open bag frame when mail is opened diff --git a/UI/BankFrame.lua b/UI/BankFrame.lua index b876140..4e2a5a9 100644 --- a/UI/BankFrame.lua +++ b/UI/BankFrame.lua @@ -1447,26 +1447,63 @@ function BankFrame:Initialize() end, "BankFrameUI") -- Update on bag changes while bank is open + -- Debounce state for BankFrame updates (prevents lag from rapid events) + local bankUpdatePending = false + local bankLockUpdatePending = false + + -- Helper to schedule a debounced BankFrame update + local function ScheduleBankFrameUpdate(delay) + if bankUpdatePending then return end + if not addon.Modules.BankScanner:IsBankOpen() then return end + if currentViewChar then return end + bankUpdatePending = true + local debounceFrame = CreateFrame("Frame") + debounceFrame.elapsed = 0 + debounceFrame:SetScript("OnUpdate", function() + this.elapsed = this.elapsed + arg1 + if this.elapsed >= delay then + this:SetScript("OnUpdate", nil) + bankUpdatePending = false + if addon.Modules.BankScanner:IsBankOpen() and not currentViewChar then + addon.Modules.BankFrame:Update() + end + end + end) + end + addon.Modules.Events:OnBagUpdate(function() if addon.Modules.BankScanner:IsBankOpen() and not currentViewChar then - addon.Modules.BankFrame:Update() + ScheduleBankFrameUpdate(0.1) end end, "BankFrameUI") - -- Update when items get locked/unlocked (for trading, mailing, etc.) + -- Update when items get locked/unlocked (debounced for trading, mailing, etc.) addon.Modules.Events:Register("ITEM_LOCK_CHANGED", function() - if addon.Modules.BankScanner:IsBankOpen() and not currentViewChar then - addon.Modules.BankFrame:Update() - end + if not addon.Modules.BankScanner:IsBankOpen() then return end + if currentViewChar then return end + if bankLockUpdatePending then return end + bankLockUpdatePending = true + local debounceFrame = CreateFrame("Frame") + debounceFrame.elapsed = 0 + debounceFrame:SetScript("OnUpdate", function() + this.elapsed = this.elapsed + arg1 + if this.elapsed >= 0.15 then + this:SetScript("OnUpdate", nil) + bankLockUpdatePending = false + if addon.Modules.BankScanner:IsBankOpen() then + addon.Modules.BankFrame:Update() + end + end + end) end, "BankFrameUI") - -- Register bank-specific update events (pfUI style) + -- Register bank-specific update events (pfUI style, debounced) local updateFrame = CreateFrame("Frame") updateFrame:RegisterEvent("PLAYERBANKSLOTS_CHANGED") updateFrame:RegisterEvent("PLAYERBANKBAGSLOTS_CHANGED") updateFrame:SetScript("OnEvent", function() if addon.Modules.BankScanner:IsBankOpen() and not currentViewChar then - addon.Modules.BankFrame:Update() + ScheduleBankFrameUpdate(0.1) end end) diff --git a/UI/QuestItemBar.lua b/UI/QuestItemBar.lua index cc51034..e84ccfa 100644 --- a/UI/QuestItemBar.lua +++ b/UI/QuestItemBar.lua @@ -24,89 +24,44 @@ local function GetScanTooltip() return scanTooltip end --- Check if an item is usable by scanning its tooltip for "Use:" -local function IsItemUsable(bagID, slotID) - if not bagID or not slotID then return false end +-- Combined function to check if an item is a quest item AND usable in ONE tooltip scan +-- This avoids the expensive double-scan that was causing lag +function QuestItemBar:CheckQuestItemUsable(bagID, slotID) + if not bagID or not slotID then return false, false, false end local tooltip = GetScanTooltip() tooltip:ClearLines() tooltip:SetBagItem(bagID, slotID) + local isQuestItem = false + local isQuestStarter = false + local isUsable = false + for i = 1, tooltip:NumLines() do local line = getglobal("Guda_QuestBarScanTooltipTextLeft" .. i) if line then local text = line:GetText() if text then local tl = string.lower(text) - -- Match explicit usability phrases (case-insensitive). Avoid generic 'quest item' matches. - if string.find(tl, "use:") or string.find(tl, "begins a quest") or string.find(tl, "starts a quest") then - return true - end - end - end - end - return false -end - --- Scan bags for quest items -function QuestItemBar:ScanForQuestItems() - questItems = {} - - local foundAny = false - -- Scan backpack and 4 bags - for bagID = 0, 4 do - local numSlots = GetContainerNumSlots(bagID) - for slotID = 1, numSlots do - local texture, count = GetContainerItemInfo(bagID, slotID) - if texture then - local isQuest, isStarter = self:IsQuestItem(bagID, slotID) - if isQuest and IsItemUsable(bagID, slotID) then - table.insert(questItems, { - bagID = bagID, - slotID = slotID, - texture = texture, - count = count - }) - foundAny = true - end - end - end - end - - -- Disabled DB fallback: only show items actually present in bags and usable - -- Fallback to database caused non-usable quest items to be shown in the bar - -- Returning early to ensure only real, usable items from bags are considered - return - -end - --- Local implementation of IsQuestItem (similar to the one in ItemButton.lua) -function QuestItemBar:IsQuestItem(bagID, slotID) - local tooltip = GetScanTooltip() - tooltip:ClearLines() - tooltip:SetBagItem(bagID, slotID) - - local isQuestItem = false - local isQuestStarter = false - - for i = 1, tooltip:NumLines() do - local line = getglobal("Guda_QuestBarScanTooltipTextLeft" .. i) - if line then - local text = line:GetText() - if text then + -- Check for quest item indicators if string.find(text, "Quest Starter") or string.find(text, "This Item Begins a Quest") or string.find(text, "Use: Starts a Quest") then isQuestItem = true isQuestStarter = true - break + isUsable = true -- Quest starters are always usable elseif string.find(text, "Quest Item") then isQuestItem = true end + -- Check for usability (case-insensitive) + if string.find(tl, "use:") or string.find(tl, "begins a quest") or string.find(tl, "starts a quest") then + isUsable = true + end end end end + -- Fallback check for quest category if not detected from tooltip if not isQuestItem then local link = GetContainerItemLink(bagID, slotID) if link and addon.Modules.Utils and addon.Modules.Utils.ExtractItemID and addon.Modules.Utils.GetItemInfoSafe then @@ -120,6 +75,37 @@ function QuestItemBar:IsQuestItem(bagID, slotID) end end + return isQuestItem, isQuestStarter, isUsable +end + +-- Scan bags for quest items (optimized: single tooltip scan per item) +function QuestItemBar:ScanForQuestItems() + questItems = {} + + -- Scan backpack and 4 bags + for bagID = 0, 4 do + local numSlots = GetContainerNumSlots(bagID) + for slotID = 1, numSlots do + local texture, count = GetContainerItemInfo(bagID, slotID) + if texture then + -- Single combined check instead of two separate tooltip scans + local isQuest, isStarter, isUsable = self:CheckQuestItemUsable(bagID, slotID) + if isQuest and isUsable then + table.insert(questItems, { + bagID = bagID, + slotID = slotID, + texture = texture, + count = count + }) + end + end + end + end +end + +-- Legacy function kept for compatibility (now calls combined function) +function QuestItemBar:IsQuestItem(bagID, slotID) + local isQuestItem, isQuestStarter, _ = self:CheckQuestItemUsable(bagID, slotID) return isQuestItem, isQuestStarter end @@ -639,9 +625,22 @@ function QuestItemBar:Initialize() frame:SetPoint(pos.point, UIParent, pos.relativePoint or pos.point, pos.x, pos.y) end - -- Register for events + -- Register for events with debouncing to prevent lag on rapid bag updates + local bagUpdatePending = false addon.Modules.Events:Register("BAG_UPDATE", function() - QuestItemBar:Update() + if bagUpdatePending then return end + bagUpdatePending = true + -- Debounce: wait 0.15 seconds before updating to batch rapid events + local debounceFrame = CreateFrame("Frame") + debounceFrame.elapsed = 0 + debounceFrame:SetScript("OnUpdate", function() + this.elapsed = this.elapsed + arg1 + if this.elapsed >= 0.15 then + this:SetScript("OnUpdate", nil) + bagUpdatePending = false + QuestItemBar:Update() + end + end) end, "QuestItemBar") addon.Modules.Events:Register("BAG_UPDATE_COOLDOWN", function() diff --git a/UI/TrackedItemBar.lua b/UI/TrackedItemBar.lua index 9580de9..8883f0c 100644 --- a/UI/TrackedItemBar.lua +++ b/UI/TrackedItemBar.lua @@ -212,9 +212,22 @@ function TrackedItemBar:Initialize() frame:SetPoint(pos.point, UIParent, pos.relativePoint or pos.point, pos.x, pos.y) end - -- Register for events + -- Register for events with debouncing to prevent lag on rapid bag updates + local bagUpdatePending = false addon.Modules.Events:Register("BAG_UPDATE", function() - TrackedItemBar:Update() + if bagUpdatePending then return end + bagUpdatePending = true + -- Debounce: wait 0.15 seconds before updating to batch rapid events + local debounceFrame = CreateFrame("Frame") + debounceFrame.elapsed = 0 + debounceFrame:SetScript("OnUpdate", function() + this.elapsed = this.elapsed + arg1 + if this.elapsed >= 0.15 then + this:SetScript("OnUpdate", nil) + bagUpdatePending = false + TrackedItemBar:Update() + end + end) end, "TrackedItemBar") addon.Modules.Events:Register("PLAYER_ENTERING_WORLD", function()