From 4d489251454688fca0b89706d4c35a3292bf5ef2 Mon Sep 17 00:00:00 2001 From: Xurkon Date: Sun, 29 Mar 2026 17:54:19 -0500 Subject: [PATCH] docs: append performance patches to v1.5.5 changelogs --- CHANGELOG.md | 5 ++ Database/Zones/zoneDB.lua | 7 +- Modules/Map/QuestieMap.lua | 6 +- Modules/Quest/QuestieQuest.lua | 14 +--- Modules/Tracker/TrackerLinePool.lua | 122 ++++++++++++---------------- docs/changelog.html | 3 + 6 files changed, 72 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ea6471..da969c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,11 @@ Addressed micro-stutters and FPS drops (190 → sub-100) reported during high-fr - **[Fix — Profiler UI]** Resolved an issue where the `QuestieProfiler` UI text was overlapping horizontally when function names were too long, rendering the time and call counts illegible. Lines are now appropriately spaced and function names are clamped and left-aligned. +- **[Perf — Math Inlining]** Removed `QuestieLib:Euclid` function call overhead from the minimap icon `FadeLogic` hot loop in favor of an inline Pythagorean distance check, reducing micro-stutters during movement. + +- **[Perf — Quest Tracker Bag Scans]** Removed O(N) nested loops iterating over the entire character inventory in `TrackerLinePool:SetItem` and `QuestieQuest:CheckQuestSourceItem`, replacing them with native O(1) `GetItemCount(itemId)` queries. Completely eliminated thousands of `GetContainerItemInfo` calls. + +- **[Perf — Tracker Cooldown Throttling]** Added a 5Hz (0.2s) execution throttle to the tracker quest item button `btn.OnUpdate` frame handler, dropping baseline `GetItemCooldown` API polls from ~6000+ checks every few minutes down to a fraction of that load. ## v1.5.4 (2026-03-29) ### Ascension Custom Zone Support diff --git a/Database/Zones/zoneDB.lua b/Database/Zones/zoneDB.lua index d93db34..5700c2b 100644 --- a/Database/Zones/zoneDB.lua +++ b/Database/Zones/zoneDB.lua @@ -139,7 +139,9 @@ function ZoneDB:GetAreaIdByUiMapId(uiMapId) -- Fast path: O(1) pre-built reverse cache local cached = uiMapIdToAreaIdCache[uiMapId] - if cached then return cached end + if cached ~= nil then + return cached ~= false and cached or nil + end -- Slow fallback: name-based lookup (only for unmapped IDs, result is cached for next time) local mapInfo = C_Map.GetMapInfo(uiMapId) @@ -158,6 +160,9 @@ function ZoneDB:GetAreaIdByUiMapId(uiMapId) if Questie.db.profile.debugEnabled then Questie:Debug(Questie.DEBUG_DEVELOP, "No AreaId found for UiMapId: " .. uiMapId .. ":" .. (mapInfo and mapInfo.name or "nil")) end + + -- We must cache that we found nothing so we don't run the slow lookup again + uiMapIdToAreaIdCache[uiMapId] = false return nil end diff --git a/Modules/Map/QuestieMap.lua b/Modules/Map/QuestieMap.lua index 0ef5ff0..3056d54 100644 --- a/Modules/Map/QuestieMap.lua +++ b/Modules/Map/QuestieMap.lua @@ -585,7 +585,7 @@ function QuestieMap:DrawWorldIcon(data, areaID, x, y, showFlag) function iconMinimap:FadeLogic() local profile = Questie.db.profile - if self.miniMapIcon and self.x and self.y and self.texture and self.UiMapID and self.texture.SetVertexColor and HBD and HBD.GetPlayerZonePosition and QuestieLib and QuestieLib.Euclid then + if self.miniMapIcon and self.x and self.y and self.texture and self.UiMapID and self.texture.SetVertexColor and HBD and HBD.GetPlayerZonePosition then if (QuestieMap.playerX and QuestieMap.playerY) then local x, y if not self.worldX then @@ -597,7 +597,9 @@ function QuestieMap:DrawWorldIcon(data, areaID, x, y, showFlag) y = self.worldY end if (x and y) then - local distance = QuestieLib:Euclid(QuestieMap.playerX, QuestieMap.playerY, x, y) / 10; + local xd = QuestieMap.playerX - x + local yd = QuestieMap.playerY - y + local distance = math.sqrt(xd * xd + yd * yd) / 10; if (distance > profile.fadeLevel) then local fade = 1 - (math.min(10, (distance - profile.fadeLevel)) * normalizedValue); diff --git a/Modules/Quest/QuestieQuest.lua b/Modules/Quest/QuestieQuest.lua index 7435c42..1304789 100644 --- a/Modules/Quest/QuestieQuest.lua +++ b/Modules/Quest/QuestieQuest.lua @@ -1090,18 +1090,12 @@ function QuestieQuest:CheckQuestSourceItem(questId, makeObjective) local sourceItemId = (quest and tonumber(quest.sourceItemId)) or 0 if quest and sourceItemId > 0 then - for bag = -2, 4 do - local numSlots = QuestieCompat.GetContainerNumSlots(bag) or 0 - for slot = 1, numSlots do - local itemId = select(10, QuestieCompat.GetContainerItemInfo(bag, slot)) - if itemId == sourceItemId then - return true - end - end - - sourceItem = false + if GetItemCount(sourceItemId) > 0 then + return true end + sourceItem = false + -- If we are missing the sourceItem for zero objective quests then make an objective for it so the -- player has a visual indication as to what item is missing and so the quest has a "tag" of some kind. -- Also double check the quests leaderboard and make sure an objective doesn't already exist. diff --git a/Modules/Tracker/TrackerLinePool.lua b/Modules/Tracker/TrackerLinePool.lua index 2a5a96a..94a5114 100644 --- a/Modules/Tracker/TrackerLinePool.lua +++ b/Modules/Tracker/TrackerLinePool.lua @@ -493,30 +493,29 @@ function TrackerLinePool.Initialize(questFrame) btn.SetItem = function(self, quest, buttonType, size) local validTexture + local foundItemId - for bag = -2, 4 do - for slot = 1, QuestieCompat.GetContainerNumSlots(bag) do - local texture, _, _, _, _, _, _, _, _, itemId = QuestieCompat.GetContainerItemInfo(bag, slot) - -- These type of quest items can never be secondary buttons - if quest.sourceItemId == itemId and QuestieDB.QueryItemSingle(itemId, "class") == 12 and buttonType == "primary" then - validTexture = texture - self.itemId = quest.sourceItemId - break - end - -- These type of quest items are technically secondary buttons but are assigned primary button slots - if (not quest.sourceItemId or quest.sourceItemId == 0) and type(quest.requiredSourceItems) == "table" and #quest.requiredSourceItems == 1 then - local questItemId = quest.requiredSourceItems[1] - if questItemId and questItemId ~= quest.sourceItemId and QuestieDB.QueryItemSingle(questItemId, "class") == 12 and questItemId == itemId then - validTexture = texture - self.itemId = questItemId - break + -- Check primary source item + if quest.sourceItemId and quest.sourceItemId ~= 0 and buttonType == "primary" then + if QuestieDB.QueryItemSingle(quest.sourceItemId, "class") == 12 and GetItemCount(quest.sourceItemId, false, false) > 0 then + foundItemId = quest.sourceItemId + end + end + + -- Check secondary source items + if not foundItemId and type(quest.requiredSourceItems) == "table" then + if #quest.requiredSourceItems == 1 then + local questItemId = quest.requiredSourceItems[1] + if questItemId and questItemId ~= quest.sourceItemId and QuestieDB.QueryItemSingle(questItemId, "class") == 12 then + if GetItemCount(questItemId, false, false) > 0 then + foundItemId = questItemId end - -- These type of quest items can never be primary buttons - elseif type(quest.requiredSourceItems) == "table" and #quest.requiredSourceItems > 1 then - for _, questItemId in pairs(quest.requiredSourceItems) do - if questItemId and questItemId ~= quest.sourceItemId and QuestieDB.QueryItemSingle(questItemId, "class") == 12 and questItemId == itemId and buttonType == "secondary" then - validTexture = texture - self.itemId = questItemId + end + elseif #quest.requiredSourceItems > 1 and buttonType == "secondary" then + for _, questItemId in pairs(quest.requiredSourceItems) do + if questItemId and questItemId ~= quest.sourceItemId and QuestieDB.QueryItemSingle(questItemId, "class") == 12 then + if GetItemCount(questItemId, false, false) > 0 then + foundItemId = questItemId break end end @@ -524,35 +523,9 @@ function TrackerLinePool.Initialize(questFrame) end end - -- Edge case to find "equipped" quest items since they will no longer be in the players bag - if (not validTexture) then - for inventorySlot = 1, 19 do - local itemId = GetInventoryItemID("player", inventorySlot) - -- These type of quest items can never be secondary buttons - if quest.sourceItemId == itemId and QuestieDB.QueryItemSingle(itemId, "class") == 12 and buttonType == "primary" then - validTexture = GetInventoryItemTexture("player", inventorySlot) - self.itemId = quest.sourceItemId - break - end - -- These type of quest items are technically secondary buttons but are assigned primary button slots - if type(quest.requiredSourceItems) == "table" and #quest.requiredSourceItems == 1 then - local questItemId = quest.requiredSourceItems[1] - if questItemId and questItemId ~= quest.sourceItemId and QuestieDB.QueryItemSingle(questItemId, "class") == 12 and questItemId == itemId then - validTexture = GetInventoryItemTexture("player", inventorySlot) - self.itemId = questItemId - break - end - -- These type of quest items can never be primary buttons - elseif type(quest.requiredSourceItems) == "table" and #quest.requiredSourceItems > 1 then - for _, questItemId in pairs(quest.requiredSourceItems) do - if questItemId and questItemId ~= quest.sourceItemId and QuestieDB.QueryItemSingle(questItemId, "class") == 12 and questItemId == itemId and buttonType == "secondary" then - validTexture = GetInventoryItemTexture("player", inventorySlot) - self.itemId = questItemId - break - end - end - end - end + if foundItemId then + self.itemId = foundItemId + validTexture = select(10, GetItemInfo(foundItemId)) or GetItemIcon(foundItemId) end if validTexture and self.itemId then @@ -617,31 +590,36 @@ function TrackerLinePool.Initialize(questFrame) return end - local start, duration, enabled = QuestieCompat.GetItemCooldown(self.itemId) + self.updateTimer = (self.updateTimer or 0) + elapsed + if self.updateTimer >= 0.2 then + local start, duration, enabled = QuestieCompat.GetItemCooldown(self.itemId) - if enabled == 1 and duration > 0 then - cooldown:SetCooldown(start, duration, enabled) - cooldown:Show() - else - cooldown:Hide() - end - - local charges = GetItemCount(self.itemId, nil, true) - if (not charges or charges ~= self.charges) then - self.count:Hide() - self.charges = GetItemCount(self.itemId, nil, true) - if self.charges > 1 then - self.count:SetText(self.charges) - self.count:Show() + if enabled == 1 and duration > 0 then + cooldown:SetCooldown(start, duration, enabled) + cooldown:Show() + else + cooldown:Hide() end - if self.charges == 0 then - Questie:Debug(Questie.DEBUG_DEVELOP, "[TrackerLinePool: Button.OnUpdate]") - QuestieCombatQueue:Queue(function() - C_Timer.After(0.2, function() - QuestieTracker:Update() + + local charges = GetItemCount(self.itemId, nil, true) + if (not charges or charges ~= self.charges) then + self.count:Hide() + self.charges = GetItemCount(self.itemId, nil, true) + if self.charges > 1 then + self.count:SetText(self.charges) + self.count:Show() + end + if self.charges == 0 then + Questie:Debug(Questie.DEBUG_DEVELOP, "[TrackerLinePool: Button.OnUpdate]") + QuestieCombatQueue:Queue(function() + C_Timer.After(0.2, function() + QuestieTracker:Update() + end) end) - end) + end end + + self.updateTimer = 0 end if UnitExists("target") then diff --git a/docs/changelog.html b/docs/changelog.html index 1c1cd73..0086f06 100644 --- a/docs/changelog.html +++ b/docs/changelog.html @@ -184,6 +184,9 @@
  • [Performance — Network Refactor] Refactored QuestieLearnerComms: reduced compression overhead (negligible size difference but massive CPU savings), slowed processing ticker to match output rate limits, and optimized duplicate detection to O(1).
  • [Performance — Arrow Closures] Hoisted heavy inner closures out of the hot QuestieArrow:UpdateNearestTargets loop to module-level functions, eliminating persistent memory allocation spikes and Garbage Collection (GC) pressure.
  • [Fix — Profiler Display] Fixed textual overlap and vertical spacing issues in the QuestieProfiler UI. Function names are now properly left-aligned, and horizontal spacing ensures metrics remain legible regardless of function name length.
  • +
  • [Performance — Math Inlining] Removed QuestieLib:Euclid function call overhead from the minimap icon FadeLogic hot loop in favor of an inline Pythagorean distance check, reducing micro-stutters during movement.
  • +
  • [Performance — Quest Tracker Bag Scans] Removed O(N) nested loops iterating over the entire character inventory in TrackerLinePool:SetItem and QuestieQuest:CheckQuestSourceItem, replacing them with native O(1) GetItemCount(itemId) queries. Completely eliminated thousands of GetContainerItemInfo calls.
  • +
  • [Performance — Tracker Cooldown Throttling] Added a 5Hz (0.2s) execution throttle to the tracker quest item button btn.OnUpdate frame handler, dropping baseline GetItemCooldown API polls from over 6000 checks every few minutes down to a fraction of that load.