From 59e3701d798ea9e35470b6ca7ccf670d61501a51 Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Thu, 1 Oct 2026 22:27:41 +0200 Subject: [PATCH 01/10] perf: pre-allocate raid unit tokens to optimize HealBot_FindUnitID performance --- HealBot.lua | 29 +++++++++++++++-------------- README.md | 3 +++ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/HealBot.lua b/HealBot.lua index 00befc2..8fb80b3 100644 --- a/HealBot.lua +++ b/HealBot.lua @@ -222,25 +222,26 @@ end -- SpiBonus and GetBonus functions moved to HealBot_Controller_Spells.lua +local HealBot_RaidUnitTokens = {} +for i=1, 40 do HealBot_RaidUnitTokens[i] = "raid"..i end + -- HealBot_FindUnitID: Finds a unit token for a given player name. function HealBot_FindUnitID(unitname) local text; - for _,unit in ipairs(HealBot_Action_HealGroup) do - text = UnitName(unit); - if text then - if text==unitname then - return unit; - end - end - end - for i=1,40 do - local unit = "raid"..i; - text = UnitName(unit); - if text then - if text==unitname then + if HealBot_Action_HealGroup then + for _,unit in ipairs(HealBot_Action_HealGroup) do + text = UnitName(unit); + if text and text==unitname then return unit; end - end + end + end + for i=1,40 do + local unit = HealBot_RaidUnitTokens[i]; + text = UnitName(unit); + if text and text==unitname then + return unit; + end end return nil; end diff --git a/README.md b/README.md index a0a402c..2066e26 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,9 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ ### Change Log +**v1.7.4** +* **Performance Fix - Unit ID Lookup** - Optimized `HealBot_FindUnitID` by pre-allocating raid unit tokens, preventing massive string concatenation and garbage collection spam during high-frequency combat events like `CHAT_MSG_ADDON` (HealComm). + **v1.7.3** * **Performance Fix - Table Pooling Part 2** - Fixed remaining un-pooled table allocations in layout updates, macro parsing, nampower integration, and aura tracking by utilizing HealBot_GetTable(). * **Performance Fix - HealComm Networking** - Fixed issue where incoming `HealComm` messages triggered synchronous UI redraws, causing massive lag in 40-man raids during combat. `HealBot_RecalcHeals` is now decoupled and deferred through the `HealBot_OnUpdate` dirty queue, allowing multiple network events to collapse into a single frame redraw. From 820f23c3f20f5cd5c71aba2d6420a718f0b36853 Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Fri, 2 Oct 2026 08:48:26 +0200 Subject: [PATCH 02/10] perf: optimize performance by caching aura tooltips and range checks while reducing redundant layout updates --- HealBot_Controller_Aura.lua | 20 ++++++++++++++++---- HealBot_Controller_Events.lua | 7 ------- HealBot_Controller_Range.lua | 17 +++++++++++++++++ HealBot_Controller_Spells.lua | 1 - HealBot_View_Layout.lua | 35 +++++++++++++++++++++++++---------- README.md | 3 +++ 6 files changed, 61 insertions(+), 22 deletions(-) diff --git a/HealBot_Controller_Aura.lua b/HealBot_Controller_Aura.lua index 597adee..c815279 100644 --- a/HealBot_Controller_Aura.lua +++ b/HealBot_Controller_Aura.lua @@ -107,13 +107,25 @@ function HealBot_CheckBuffs(unit) hasBuff[k] = nil end local i = 1 + if not HealBot_TextureToName then HealBot_TextureToName = {} end while true do local buffTexture = UnitBuff(unit, i) if not buffTexture then break end - HealBot_ScanTooltip:SetOwner(UIParent, "ANCHOR_NONE") - HealBot_ScanTooltip:ClearLines() - HealBot_ScanTooltip:SetUnitBuff(unit, i) - local buffName = HealBot_ScanTooltipTextLeft1:GetText() + + local buffName = HealBot_TextureToName[buffTexture] + if buffName == nil then + HealBot_ScanTooltip:SetOwner(UIParent, "ANCHOR_NONE") + HealBot_ScanTooltip:ClearLines() + HealBot_ScanTooltip:SetUnitBuff(unit, i) + local textObj = HealBot_ScanTooltipTextLeft1 + if textObj then + buffName = textObj:GetText() + end + if buffName then + HealBot_TextureToName[buffTexture] = buffName + end + end + if buffName then hasBuff[buffName] = true end diff --git a/HealBot_Controller_Events.lua b/HealBot_Controller_Events.lua index ab5521e..24f8983 100644 --- a/HealBot_Controller_Events.lua +++ b/HealBot_Controller_Events.lua @@ -173,13 +173,6 @@ function HealBot_OnUpdate(this, arg1) HealBot_Action_RefreshPower(unitID) end - local powerID, _ = next(HealBot_View_DirtyPower) - while powerID do - HealBot_Action_RefreshPower(powerID) - HealBot_View_DirtyPower[powerID] = nil - powerID, _ = next(HealBot_View_DirtyPower) - end - if HealBot_EquipChangeTimer > 0 then HealBot_EquipChangeTimer = HealBot_EquipChangeTimer - arg1 if HealBot_EquipChangeTimer <= 0 then diff --git a/HealBot_Controller_Range.lua b/HealBot_Controller_Range.lua index f37c7b2..4ccb085 100644 --- a/HealBot_Controller_Range.lua +++ b/HealBot_Controller_Range.lua @@ -8,12 +8,27 @@ function HealBot_ResetRangeScale() _scale = 0 end +local _rangeCache = {} +local _lastFrameTime = 0 + -- HealBot_Range_Check: Checks if unit is in range via map distance or API. function HealBot_Range_Check(unit, range) + local currentTime = GetTime() + if _lastFrameTime ~= currentTime then + for k in pairs(_rangeCache) do _rangeCache[k] = nil end + _lastFrameTime = currentTime + end + local return_val = 0; if not range then range = 40; end + + local cacheKey = unit .. "_" .. range + if _rangeCache[cacheKey] ~= nil then + return _rangeCache[cacheKey] + end + if ( unit == "player" ) then return_val = 1; elseif HealBot_Integrations_ClassicAPI_Active and UnitDistanceSquared and UnitInLineOfSight then @@ -56,5 +71,7 @@ function HealBot_Range_Check(unit, range) end end end + + _rangeCache[cacheKey] = return_val return return_val; end diff --git a/HealBot_Controller_Spells.lua b/HealBot_Controller_Spells.lua index b9bea6c..b35b0cc 100644 --- a/HealBot_Controller_Spells.lua +++ b/HealBot_Controller_Spells.lua @@ -648,7 +648,6 @@ function HealBot_RecalcParty() end end end - HealBot_Action_RefreshButtons(); end -- HealBot_RecalcSpells: Recalculates spells and updates layout. diff --git a/HealBot_View_Layout.lua b/HealBot_View_Layout.lua index 25179d3..f8b35b4 100644 --- a/HealBot_View_Layout.lua +++ b/HealBot_View_Layout.lua @@ -246,23 +246,38 @@ function HealBot_Action_EnableButton(button) bar.txt:SetTextColor(sr, sg, sb, sa); local fontName, fontHeight, fontFlags = bar.txt:GetFont() local fontOutline = HealBot_Config.bfontoutline[HealBot_Config.Current_Skin] or 0 - if fontOutline == 1 then - bar.txt:SetFont(fontName, fontHeight, "OUTLINE") - else - bar.txt:SetFont(fontName, fontHeight, "") + local expectedFlags = fontOutline == 1 and "OUTLINE" or "" + if fontFlags ~= expectedFlags then + bar.txt:SetFont(fontName, fontHeight, expectedFlags) end local iconSize = HealBot_Config.biconsize[HealBot_Config.Current_Skin] or 12 for i = 1, 10 do - local icon = getglobal(button:GetName() .. "BarIcon" .. i) + local icon = button["icon"..i] + if not icon then + icon = getglobal(button:GetName() .. "BarIcon" .. i) + button["icon"..i] = icon + end if icon then if HealBot_UnitIcons and HealBot_UnitIcons[unit] and HealBot_UnitIcons[unit][i] then - icon:SetTexture(HealBot_UnitIcons[unit][i]) - icon:SetWidth(iconSize) - icon:SetHeight(iconSize) - icon:Show() + local currentTexture = icon:GetTexture() + local newTexture = HealBot_UnitIcons[unit][i] + if currentTexture ~= newTexture then + icon:SetTexture(newTexture) + end + + if icon:GetWidth() ~= iconSize then + icon:SetWidth(iconSize) + icon:SetHeight(iconSize) + end + + if not icon:IsVisible() then + icon:Show() + end else - icon:Hide() + if icon:IsVisible() then + icon:Hide() + end end end end diff --git a/README.md b/README.md index 2066e26..75846d3 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,9 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ ### Change Log **v1.7.4** +* **Performance Fix - CPU Spikes** - Fixed massive FPS drops (stuttering to 8-16 FPS) during raid combat. The `HealBot_CheckBuffs` function previously forced the client to generate hidden UI tooltips for up to 32 buffs per player on every single `UNIT_AURA` event. Tooltip parsing is now cached per texture, bypassing thousands of redundant API calls per second. +* **Performance Fix - Frame Rendering** - Eliminated redundant UI layout recalculations in `HealBot_Action_EnableButton`. The UI no longer forces `SetFont`, `SetWidth`, `SetHeight`, and `GetGlobal` lookups on every single combat tick (which previously caused 4,000+ layout updates per second in 40-man raids). +* **Performance Fix - Range Checking** - Greatly reduced CPU stutter caused by range calculations. `HealBot_Range_Check` now caches map positioning math and API calls per-frame, preventing the addon from calculating the exact same `math.sqrt` distance up to 5 times per unit during heavy combat. * **Performance Fix - Unit ID Lookup** - Optimized `HealBot_FindUnitID` by pre-allocating raid unit tokens, preventing massive string concatenation and garbage collection spam during high-frequency combat events like `CHAT_MSG_ADDON` (HealComm). **v1.7.3** From 62b93b74e24ea42cdcb8414477545da0c320c85a Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Fri, 2 Oct 2026 10:42:02 +0200 Subject: [PATCH 03/10] fix: correct French translation for Mage --- HealBot_Localization.fr.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HealBot_Localization.fr.lua b/HealBot_Localization.fr.lua index f53e386..56f7b67 100644 --- a/HealBot_Localization.fr.lua +++ b/HealBot_Localization.fr.lua @@ -18,7 +18,7 @@ if (GetLocale() == "frFR") then HEALBOT_DRUID = "Druide"; HEALBOT_HUNTER = "Chasseur"; -HEALBOT_MAGE = "Magier"; +HEALBOT_MAGE = "Mage"; HEALBOT_PALADIN = "Paladin"; HEALBOT_PRIEST = "Pr\195\170tre"; HEALBOT_ROGUE = "Voleur"; From f4ba0d983d0a0db241c1bdeef0e56d2762af7648 Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Fri, 2 Oct 2026 11:02:45 +0200 Subject: [PATCH 04/10] feat: add /hb debug command for local chat logging --- HealBot.lua | 15 +++++++++++++++ HealBot_Controller_Comms.lua | 9 ++++++++- HealBot_Data.lua | 1 + HealBot_Integrations.lua | 1 + README.md | 2 ++ 5 files changed, 27 insertions(+), 1 deletion(-) diff --git a/HealBot.lua b/HealBot.lua index 8fb80b3..f2cbc62 100644 --- a/HealBot.lua +++ b/HealBot.lua @@ -103,6 +103,21 @@ function HealBot_SlashCmd(cmd) HealBot_AddDebug( "Channel active" ); return; end + if (cmd=="debug" or cmd=="dbg") then + if not HealBot_Config then return; end + if HealBot_Config.DebugMode == 1 then + HealBot_Config.DebugMode = 0; + if DEFAULT_CHAT_FRAME then + DEFAULT_CHAT_FRAME:AddMessage("|cFFFFD100HealBot|r: Debug mode |cFFFF0000DISABLED|r."); + end + else + HealBot_Config.DebugMode = 1; + if DEFAULT_CHAT_FRAME then + DEFAULT_CHAT_FRAME:AddMessage("|cFFFFD100HealBot|r: Debug mode |cFF00FF00ENABLED|r."); + end + end + return; + end end -- HealBot_SendAddonMessage moved to HealBot_Controller_Comms.lua diff --git a/HealBot_Controller_Comms.lua b/HealBot_Controller_Comms.lua index 47033dd..dded2b2 100644 --- a/HealBot_Controller_Comms.lua +++ b/HealBot_Controller_Comms.lua @@ -30,8 +30,15 @@ function HealBot_AddChat(msg) end end --- HealBot_AddDebug: Prints debug messages to internal chat channel. +-- HealBot_AddDebug: Prints debug messages to chat or internal debug channel. function HealBot_AddDebug(msg) + if HealBot_Config and HealBot_Config.DebugMode == 1 then + if DEFAULT_CHAT_FRAME then + DEFAULT_CHAT_FRAME:AddMessage("|cFFFFD100[HB Debug]|r " .. msg); + end + return; + end + local chanid = HealBot_Get_DebugChan(); if chanid and HealBot_SpamCnt < 3 then HealBot_SpamCnt = HealBot_SpamCnt + 1; diff --git a/HealBot_Data.lua b/HealBot_Data.lua index 5695567..5894ef4 100644 --- a/HealBot_Data.lua +++ b/HealBot_Data.lua @@ -1,4 +1,5 @@ HealBot_ConfigDefaults = { + DebugMode = 0, ShowManaBars=0, ManaBarsHealersOnly=0, Version = HEALBOT_VERSION, diff --git a/HealBot_Integrations.lua b/HealBot_Integrations.lua index 42ac6c2..e45554f 100644 --- a/HealBot_Integrations.lua +++ b/HealBot_Integrations.lua @@ -21,6 +21,7 @@ function HealBot_Integrations_Toggle() HealBot_Integrations_UnitXP_Active = true; HealBot_AddDebug("UnitXP Integration: ENABLED"); else + Look at the lines that need translation HealBot_Integrations_UnitXP_Active = false; HealBot_AddDebug("UnitXP Integration: DISABLED"); end diff --git a/README.md b/README.md index 75846d3..e81c524 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ * `/hb` - Toggles the main HealBot panel on and off * `/hb options` - Toggles the HealBot options panel on and off * `/hb reset` - Resets the contents of the main HealBot panel +* `/hb debug` - Toggles in-game debug message output to the main chat frame ### Key Features & Current Functionality * **MVC & Observer Architecture:** High-performance, reactive engine that updates frames dynamically on state changes, drastically reducing CPU and memory overhead. Features memory recycling pool and event loop decoupling for zero-stutter combat. @@ -54,6 +55,7 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ * **Performance Fix - Frame Rendering** - Eliminated redundant UI layout recalculations in `HealBot_Action_EnableButton`. The UI no longer forces `SetFont`, `SetWidth`, `SetHeight`, and `GetGlobal` lookups on every single combat tick (which previously caused 4,000+ layout updates per second in 40-man raids). * **Performance Fix - Range Checking** - Greatly reduced CPU stutter caused by range calculations. `HealBot_Range_Check` now caches map positioning math and API calls per-frame, preventing the addon from calculating the exact same `math.sqrt` distance up to 5 times per unit during heavy combat. * **Performance Fix - Unit ID Lookup** - Optimized `HealBot_FindUnitID` by pre-allocating raid unit tokens, preventing massive string concatenation and garbage collection spam during high-frequency combat events like `CHAT_MSG_ADDON` (HealComm). +* **Debug Output Command** - Added `/hb debug` (or `/hb dbg`) slash command to toggle real-time debug message printing directly into the standard chat frame without requiring custom chat channels. **v1.7.3** * **Performance Fix - Table Pooling Part 2** - Fixed remaining un-pooled table allocations in layout updates, macro parsing, nampower integration, and aura tracking by utilizing HealBot_GetTable(). From 032c7571afd3209bfc8fd4efcf8a6f6750b4b67e Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Fri, 2 Oct 2026 12:17:29 +0200 Subject: [PATCH 05/10] perf: optimize spell and unit ID lookups with caching and fix range cache memory leaks --- HealBot.lua | 47 ++++++++++++++++++++++------------- HealBot_Controller_Events.lua | 2 ++ HealBot_Controller_Range.lua | 14 +++++++---- HealBot_Controller_Spells.lua | 38 ++++++++++++++-------------- HealBot_Integrations.lua | 1 - README.md | 5 ++++ 6 files changed, 64 insertions(+), 43 deletions(-) diff --git a/HealBot.lua b/HealBot.lua index f2cbc62..7c37104 100644 --- a/HealBot.lua +++ b/HealBot.lua @@ -240,25 +240,38 @@ end local HealBot_RaidUnitTokens = {} for i=1, 40 do HealBot_RaidUnitTokens[i] = "raid"..i end +HealBot_NameToUnitID = {} + +function HealBot_UpdateUnitIDCache() + HealBot_NameToUnitID = {} + local name = UnitName("player") + if name then HealBot_NameToUnitID[name] = "player" end + + for i=1, 4 do + local unit = "party"..i + name = UnitName(unit) + if name then HealBot_NameToUnitID[name] = unit end + unit = "partypet"..i + name = UnitName(unit) + if name then HealBot_NameToUnitID[name] = unit end + end + + local petName = UnitName("pet") + if petName then HealBot_NameToUnitID[petName] = "pet" end + + for i=1, 40 do + local unit = "raid"..i + name = UnitName(unit) + if name then HealBot_NameToUnitID[name] = unit end + unit = "raidpet"..i + name = UnitName(unit) + if name then HealBot_NameToUnitID[name] = unit end + end +end + -- HealBot_FindUnitID: Finds a unit token for a given player name. function HealBot_FindUnitID(unitname) - local text; - if HealBot_Action_HealGroup then - for _,unit in ipairs(HealBot_Action_HealGroup) do - text = UnitName(unit); - if text and text==unitname then - return unit; - end - end - end - for i=1,40 do - local unit = HealBot_RaidUnitTokens[i]; - text = UnitName(unit); - if text and text==unitname then - return unit; - end - end - return nil; + return HealBot_NameToUnitID[unitname] end -- HealBot_PlaySound: Plays an audio alert based on ID. diff --git a/HealBot_Controller_Events.lua b/HealBot_Controller_Events.lua index 24f8983..71dfc9d 100644 --- a/HealBot_Controller_Events.lua +++ b/HealBot_Controller_Events.lua @@ -498,6 +498,7 @@ end function HealBot_OnEvent_PartyMembersChanged(this) HealBot_Model:PreserveStateByGUID() HealBot_Integrations_PruneNampower() + if HealBot_UpdateUnitIDCache then HealBot_UpdateUnitIDCache() end if HealBot_IsFighting then HealBot_Action_PartyChanged() end @@ -557,6 +558,7 @@ end function HealBot_OnEvent_SpellsChanged(this, arg1) if arg1 then return; end HealBot_AddDebug("HB: SpellsChanged"); + if HealBot_CacheSpellIDs then HealBot_CacheSpellIDs() end HealBot_SpellsInitFlag = 2; end diff --git a/HealBot_Controller_Range.lua b/HealBot_Controller_Range.lua index 4ccb085..823a053 100644 --- a/HealBot_Controller_Range.lua +++ b/HealBot_Controller_Range.lua @@ -15,7 +15,11 @@ local _lastFrameTime = 0 function HealBot_Range_Check(unit, range) local currentTime = GetTime() if _lastFrameTime ~= currentTime then - for k in pairs(_rangeCache) do _rangeCache[k] = nil end + for r, cacheTable in pairs(_rangeCache) do + for u in pairs(cacheTable) do + cacheTable[u] = nil + end + end _lastFrameTime = currentTime end @@ -24,9 +28,9 @@ function HealBot_Range_Check(unit, range) range = 40; end - local cacheKey = unit .. "_" .. range - if _rangeCache[cacheKey] ~= nil then - return _rangeCache[cacheKey] + _rangeCache[range] = _rangeCache[range] or {} + if _rangeCache[range][unit] ~= nil then + return _rangeCache[range][unit] end if ( unit == "player" ) then @@ -72,6 +76,6 @@ function HealBot_Range_Check(unit, range) end end - _rangeCache[cacheKey] = return_val + _rangeCache[range][unit] = return_val return return_val; end diff --git a/HealBot_Controller_Spells.lua b/HealBot_Controller_Spells.lua index b35b0cc..e6dd71a 100644 --- a/HealBot_Controller_Spells.lua +++ b/HealBot_Controller_Spells.lua @@ -34,28 +34,26 @@ function HealBot_GetSpellName(id) return spellName .. " (" .. subSpellName .. ")"; end +HealBot_SpellNameToId = {} + +function HealBot_CacheSpellIDs() + HealBot_SpellNameToId = {} + local id = 1 + while true do + local spellName, subSpellName = GetSpellName(id, BOOKTYPE_SPELL) + if not spellName then break end + if subSpellName and subSpellName ~= "" then + HealBot_SpellNameToId[spellName .. " (" .. subSpellName .. ")"] = id + HealBot_SpellNameToId[spellName .. "(" .. subSpellName .. ")"] = id + end + HealBot_SpellNameToId[spellName] = id + id = id + 1 + end +end + -- HealBot_GetSpellId: Internal utility: HealBot_GetSpellId function HealBot_GetSpellId(spell) - local id, idd = 1, 0; - while true do - local spellName, subSpellName = GetSpellName(id, BOOKTYPE_SPELL); - if (spellName) then - if (spell == spellName .. " (" .. subSpellName .. ")") or (spell == spellName .. "(" .. subSpellName .. ")") then - return id; - end - if (spell == spellName) then - idd = id; - end - else - break - end - id = id + 1; - end - if idd > 0 then - return idd - else - return nil; - end + return HealBot_SpellNameToId[spell] end -- HealBot_CastSpellByName: Internal utility: HealBot_CastSpellByName diff --git a/HealBot_Integrations.lua b/HealBot_Integrations.lua index e45554f..42ac6c2 100644 --- a/HealBot_Integrations.lua +++ b/HealBot_Integrations.lua @@ -21,7 +21,6 @@ function HealBot_Integrations_Toggle() HealBot_Integrations_UnitXP_Active = true; HealBot_AddDebug("UnitXP Integration: ENABLED"); else - Look at the lines that need translation HealBot_Integrations_UnitXP_Active = false; HealBot_AddDebug("UnitXP Integration: DISABLED"); end diff --git a/README.md b/README.md index e81c524..8658b72 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,11 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ * **Chat Announcements & Mouseover Casts:** Chat announcements are now correctly fired when using native Hovercasting (Mouseover). Additionally, added safety checks to prevent spamming chat announcements when accidentally clicking on a dead player (unless casting a resurrection spell). * **New Priest Buff (Enlighten):** Added the custom "Enlighten" buff to Priest trackable buffs, complete with the `btnholyscriptures` icon on unit frames. +**1.3.4** +* **Performance: Spell Lookup Optimization:** Fixed a massive CPU drain and garbage generation issue where the entire spellbook was linearly scanned every frame per unit. Spell IDs are now cached via O(1) dictionary lookup on login and talent/spell changes. +* **Performance: Unit ID Lookup Optimization:** Eliminated heavy C-API calls to `UnitName()` during incoming heals by caching raid and party unit tokens in a fast O(1) hash table that updates on roster changes. +* **Performance: Range Cache GC Leak:** Fixed a continuous memory leak in the range checker caused by string concatenation (`unit .. "_" .. range`) running every frame. Range states are now stored in a nested numeric table cache. +* **Integrations Bug Fix:** Removed stray translation text from `HealBot_Integrations.lua` that caused Lua syntax errors on load. **1.3.3** * **Chat Panel Refactor:** Completely replaced the spell selection dropdowns in the Chat tab with manual text input boxes, allowing users to type exact spell names (with or without ranks) directly, matching the behavior of the key-bindings configuration. Fixed a string matching bug caused by Vanilla WoW's hidden trailing spaces when comparing spell names, ensuring that both rankless (e.g. `Flash Heal`) and ranked inputs trigger correctly. From 526a4f1b9bbc0de251429d4d410d604f6eebed08 Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Fri, 2 Oct 2026 12:51:31 +0200 Subject: [PATCH 06/10] docs: update README changelog with recent performance and bug fixes --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8658b72..ca8568c 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,10 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ * **Performance Fix - Range Checking** - Greatly reduced CPU stutter caused by range calculations. `HealBot_Range_Check` now caches map positioning math and API calls per-frame, preventing the addon from calculating the exact same `math.sqrt` distance up to 5 times per unit during heavy combat. * **Performance Fix - Unit ID Lookup** - Optimized `HealBot_FindUnitID` by pre-allocating raid unit tokens, preventing massive string concatenation and garbage collection spam during high-frequency combat events like `CHAT_MSG_ADDON` (HealComm). * **Debug Output Command** - Added `/hb debug` (or `/hb dbg`) slash command to toggle real-time debug message printing directly into the standard chat frame without requiring custom chat channels. +* **Performance Fix - Spell Lookup** - Fixed a massive CPU drain and garbage generation issue where the entire spellbook was linearly scanned every frame per unit. Spell IDs are now cached via O(1) dictionary lookup on login and talent/spell changes. +* **Performance Fix - Unit Hash Lookup** - Eliminated heavy C-API calls to `UnitName()` during incoming heals by caching raid and party unit tokens in a fast O(1) hash table that updates on roster changes. +* **Performance Fix - Range Cache GC Leak** - Fixed a continuous memory leak in the range checker caused by string concatenation (`unit .. "_" .. range`) running every frame. Range states are now stored in a nested numeric table cache. +* **Integrations Bug Fix** - Removed stray translation text from `HealBot_Integrations.lua` that caused Lua syntax errors on load. **v1.7.3** * **Performance Fix - Table Pooling Part 2** - Fixed remaining un-pooled table allocations in layout updates, macro parsing, nampower integration, and aura tracking by utilizing HealBot_GetTable(). @@ -152,12 +156,6 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ * **Chat Announcements & Mouseover Casts:** Chat announcements are now correctly fired when using native Hovercasting (Mouseover). Additionally, added safety checks to prevent spamming chat announcements when accidentally clicking on a dead player (unless casting a resurrection spell). * **New Priest Buff (Enlighten):** Added the custom "Enlighten" buff to Priest trackable buffs, complete with the `btnholyscriptures` icon on unit frames. -**1.3.4** -* **Performance: Spell Lookup Optimization:** Fixed a massive CPU drain and garbage generation issue where the entire spellbook was linearly scanned every frame per unit. Spell IDs are now cached via O(1) dictionary lookup on login and talent/spell changes. -* **Performance: Unit ID Lookup Optimization:** Eliminated heavy C-API calls to `UnitName()` during incoming heals by caching raid and party unit tokens in a fast O(1) hash table that updates on roster changes. -* **Performance: Range Cache GC Leak:** Fixed a continuous memory leak in the range checker caused by string concatenation (`unit .. "_" .. range`) running every frame. Range states are now stored in a nested numeric table cache. -* **Integrations Bug Fix:** Removed stray translation text from `HealBot_Integrations.lua` that caused Lua syntax errors on load. - **1.3.3** * **Chat Panel Refactor:** Completely replaced the spell selection dropdowns in the Chat tab with manual text input boxes, allowing users to type exact spell names (with or without ranks) directly, matching the behavior of the key-bindings configuration. Fixed a string matching bug caused by Vanilla WoW's hidden trailing spaces when comparing spell names, ensuring that both rankless (e.g. `Flash Heal`) and ranked inputs trigger correctly. * **Chat Layout Stack:** Restructured the Chat Options XML layout to correctly stack the inputs into neat rows, preventing horizontal overlap and providing more space for typing long announce messages. From 2a0d607bcd473394989a2f2a723fd13d9ed1788a Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:54:00 +0200 Subject: [PATCH 07/10] fix: improve performance and safety with null checks and talent rank caching --- HealBot.lua | 1 + HealBotBlue.toc | 2 +- HealBot_Controller_Events.lua | 2 ++ HealBot_Controller_Spells.lua | 39 ++++++++++++++++------------------- README.md | 1 + 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/HealBot.lua b/HealBot.lua index 7c37104..93e6548 100644 --- a/HealBot.lua +++ b/HealBot.lua @@ -271,6 +271,7 @@ end -- HealBot_FindUnitID: Finds a unit token for a given player name. function HealBot_FindUnitID(unitname) + if not unitname then return nil end return HealBot_NameToUnitID[unitname] end diff --git a/HealBotBlue.toc b/HealBotBlue.toc index 30b9bda..4a508a7 100644 --- a/HealBotBlue.toc +++ b/HealBotBlue.toc @@ -1,6 +1,6 @@ ## Interface: 11200 ## Title: HealBotBlue -## Version: 1.7.3 +## Version: 1.7.4 ## Author: Bluewhale ## Notes: Raidframe replacement for healing, decursing, resource tracking and buffing with optional support for SuperWow, ClassicAPI, UnitXP_SP3 and Nampower. ## SavedVariablesPerCharacter: HealBot_Config diff --git a/HealBot_Controller_Events.lua b/HealBot_Controller_Events.lua index 71dfc9d..c096839 100644 --- a/HealBot_Controller_Events.lua +++ b/HealBot_Controller_Events.lua @@ -565,6 +565,7 @@ end -- HealBot_OnEvent_TalentsChanged: Internal utility: HealBot_OnEvent_TalentsChanged function HealBot_OnEvent_TalentsChanged(this, arg1) HealBot_AddDebug("HB: TalentsChanged"); + if HealBot_UpdateTalentRanks then HealBot_UpdateTalentRanks() end end -- HealBot_OnEvent_PlayerEnteringWorld: Internal utility: HealBot_OnEvent_PlayerEnteringWorld @@ -574,6 +575,7 @@ function HealBot_OnEvent_PlayerEnteringWorld(this) if HealBot_ApplyRefreshHook then HealBot_ApplyRefreshHook() end + if HealBot_UpdateUnitIDCache then HealBot_UpdateUnitIDCache() end end -- HealBot_OnEvent_SpellcastStart: Internal utility: HealBot_OnEvent_SpellcastStart diff --git a/HealBot_Controller_Spells.lua b/HealBot_Controller_Spells.lua index e6dd71a..82ee3a6 100644 --- a/HealBot_Controller_Spells.lua +++ b/HealBot_Controller_Spells.lua @@ -53,6 +53,7 @@ end -- HealBot_GetSpellId: Internal utility: HealBot_GetSpellId function HealBot_GetSpellId(spell) + if not spell then return nil end return HealBot_SpellNameToId[spell] end @@ -210,18 +211,6 @@ end -- HealBot_StartCasting: Initiates spell cast and broadcasts incoming heal. function HealBot_StartCasting(spell, target, ttype) HealBot_CastFailed = false; - - -- Extract base spell for internal tracking - local baseSpell = spell - local parenIndex = string.find(spell, " %(") - if parenIndex then - baseSpell = string.sub(spell, 1, parenIndex - 1) - else - parenIndex = string.find(spell, "%(") - if parenIndex then - baseSpell = string.sub(spell, 1, parenIndex - 1) - end - end HealBot_CastSpellByName(spell); if ( SpellCanTargetUnit(target) ) then @@ -236,11 +225,11 @@ function HealBot_StartCasting(spell, target, ttype) end end - if ttype == "fired" and HealBot_Spells[baseSpell] then + if ttype == "fired" and HealBot_Spells[spell] then if not HealBot_CastFailed then - HealBot_CastingSpell = baseSpell; + HealBot_CastingSpell = spell; HealBot_CastingTarget = target; - HealBot_Process_HealValue(baseSpell, target); + HealBot_Process_HealValue(spell, target); HealBot_AnnounceCast(spell, target); end end @@ -654,17 +643,24 @@ function HealBot_RecalcSpells() HealBot_RecalcParty(); end --- HealBot_GetTalentRank: Internal utility: HealBot_GetTalentRank -function HealBot_GetTalentRank(talentName) +HealBot_TalentRanks = {} + +function HealBot_UpdateTalentRanks() + HealBot_TalentRanks = {} for t = 1, GetNumTalentTabs() do for i = 1, GetNumTalents(t) do - local nameTalent, icon, tier, column, currRank, maxRank = GetTalentInfo(t, i); - if nameTalent == talentName then - return currRank; + local nameTalent, icon, tier, column, currRank, maxRank = GetTalentInfo(t, i) + if nameTalent then + HealBot_TalentRanks[nameTalent] = currRank end end end - return 0; +end + +-- HealBot_GetTalentRank: Internal utility: HealBot_GetTalentRank +function HealBot_GetTalentRank(talentName) + if not next(HealBot_TalentRanks) then HealBot_UpdateTalentRanks() end + return HealBot_TalentRanks[talentName] or 0 end -- HealBot_SpiBonus: Internal utility: HealBot_SpiBonus @@ -699,6 +695,7 @@ end -- HealBot_InitSpells: Scans spellbook to find available heals. function HealBot_InitSpells() + HealBot_CacheSpellIDs() local id = 1 local cnt = 0; local class = HealBot_UnitClass("player") diff --git a/README.md b/README.md index ca8568c..565fa39 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ * **Performance Fix - Spell Lookup** - Fixed a massive CPU drain and garbage generation issue where the entire spellbook was linearly scanned every frame per unit. Spell IDs are now cached via O(1) dictionary lookup on login and talent/spell changes. * **Performance Fix - Unit Hash Lookup** - Eliminated heavy C-API calls to `UnitName()` during incoming heals by caching raid and party unit tokens in a fast O(1) hash table that updates on roster changes. * **Performance Fix - Range Cache GC Leak** - Fixed a continuous memory leak in the range checker caused by string concatenation (`unit .. "_" .. range`) running every frame. Range states are now stored in a nested numeric table cache. +* **Performance Fix - Talent Rank Scan** - Fixed severe CPU stutter when gaining or losing auras that affect stats. Previously, inventory updates forced a full equipment and spell recalculation which linearly scanned the entire talent tree multiple times per rank. Talent ranks are now cached natively. * **Integrations Bug Fix** - Removed stray translation text from `HealBot_Integrations.lua` that caused Lua syntax errors on load. **v1.7.3** From 97df6c9a98e9c1b8d6a86f5a408c4780d95135d2 Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Sun, 4 Oct 2026 19:24:34 +0200 Subject: [PATCH 08/10] feat: add Nampower integration for player HoT timers display for active auras --- HealBot_Controller_Aura.lua | 26 ++++++ HealBot_Controller_Comms.lua | 12 ++- HealBot_Controller_Events.lua | 10 ++ HealBot_Data.lua | 1 + HealBot_Integrations.lua | 21 ++++- HealBot_Model.lua | 4 +- HealBot_Options_Integrations.lua | 1 + HealBot_Options_Integrations.xml | 31 ++++++- HealBot_View_Layout.lua | 152 +++++++++++++++++++++++++++++++ README.md | 2 + 10 files changed, 254 insertions(+), 6 deletions(-) diff --git a/HealBot_Controller_Aura.lua b/HealBot_Controller_Aura.lua index c815279..5efcad9 100644 --- a/HealBot_Controller_Aura.lua +++ b/HealBot_Controller_Aura.lua @@ -272,9 +272,11 @@ function HealBot_OnEvent_UnitAura(this, unit) end local b = 1 + local activeBuffs = HealBot_GetTable() while true do local buff = UnitBuff(unit, b) if not buff then break end + activeBuffs[buff] = true if HealBot_TrackedHoTs[buff] and iconCount < 10 then iconCount = iconCount + 1 HealBot_UnitIcons[unit][iconCount] = buff @@ -282,6 +284,30 @@ function HealBot_OnEvent_UnitAura(this, unit) b = b + 1 end + if HealBot_Integrations_Nampower_Active and HealBot_Nampower_Auras then + local guid = HealBot_GetUnitGUID and HealBot_GetUnitGUID(unit) + if guid then + if not HealBot_Nampower_NameCache then HealBot_Nampower_NameCache = HealBot_GetTable() end + HealBot_Nampower_NameCache[guid] = UnitName(unit) + + if HealBot_Nampower_Auras[guid] then + local validHoTs = { + ["Renew"] = "Interface\\Icons\\Spell_Holy_Renew", + ["Rejuvenation"] = "Interface\\Icons\\Spell_Nature_Rejuvenation", + ["Regrowth"] = "Interface\\Icons\\Spell_Nature_ResistNature", + ["Power Word: Shield"] = "Interface\\Icons\\Spell_Holy_PowerWordShield", + } + for spellName, _ in pairs(HealBot_Nampower_Auras[guid]) do + local tex = validHoTs[spellName] + if tex and not activeBuffs[tex] then + HealBot_Nampower_Auras[guid][spellName] = nil + end + end + end + end + end + HealBot_ReleaseTable(activeBuffs) + local d = 1 while true do local debuff = UnitDebuff(unit, d) diff --git a/HealBot_Controller_Comms.lua b/HealBot_Controller_Comms.lua index dded2b2..2267662 100644 --- a/HealBot_Controller_Comms.lua +++ b/HealBot_Controller_Comms.lua @@ -143,9 +143,17 @@ function HealBot_OnEvent_AddonMsg(this, addon_id, inc_msg, dist_target, sender_i -- Clear reusable args array for i=1, 10 do HealBot_Comms_Args[i] = nil end local argCount = 0 - for word in string.gfind(inc_msg, "[^/]+") do + local start_pos = 1 + local next_pos = string.find(inc_msg, "/", start_pos, true) + while next_pos do argCount = argCount + 1 - HealBot_Comms_Args[argCount] = word + HealBot_Comms_Args[argCount] = string.sub(inc_msg, start_pos, next_pos - 1) + start_pos = next_pos + 1 + next_pos = string.find(inc_msg, "/", start_pos, true) + end + if start_pos <= string.len(inc_msg) then + argCount = argCount + 1 + HealBot_Comms_Args[argCount] = string.sub(inc_msg, start_pos) end local cmd = HealBot_Comms_Args[1] diff --git a/HealBot_Controller_Events.lua b/HealBot_Controller_Events.lua index c096839..5682a44 100644 --- a/HealBot_Controller_Events.lua +++ b/HealBot_Controller_Events.lua @@ -182,6 +182,16 @@ function HealBot_OnUpdate(this, arg1) HealBot_RecalcSpells(); end end + if HealBot_Config.HealBot_Integrations_ShowHoTTimers == 1 then + if not HealBot_HoT_Update_Timer or GetTime() >= HealBot_HoT_Update_Timer then + HealBot_HoT_Update_Timer = GetTime() + 0.25 + if HealBot_Action_UpdatePlayerHoTs then + HealBot_Action_UpdatePlayerHoTs() + end + end + elseif HealBot_PlayerHoTsListFrame and HealBot_PlayerHoTsListFrame:IsVisible() then + HealBot_PlayerHoTsListFrame:Hide() + end HealBot_Timer1 = HealBot_Timer1 + arg1; if HealBot_Timer1 >= 2.5 then diff --git a/HealBot_Data.lua b/HealBot_Data.lua index 5894ef4..a4a4c8a 100644 --- a/HealBot_Data.lua +++ b/HealBot_Data.lua @@ -261,6 +261,7 @@ HealBot_ConfigDefaults = { HealBot_Integrations_Nampower = 0, HealBot_Integrations_SuperWoW = 0, HealBot_Integrations_ClassicAPI = 0, + HealBot_Integrations_ShowHoTTimers = 0, }; HealBot_Config = {}; diff --git a/HealBot_Integrations.lua b/HealBot_Integrations.lua index 42ac6c2..65a34a7 100644 --- a/HealBot_Integrations.lua +++ b/HealBot_Integrations.lua @@ -10,6 +10,8 @@ function HealBot_GetUnitGUID(unit) return UnitGUID(unit) elseif HealBot_Integrations_SuperWoW_Active and GetUnitGUID then return GetUnitGUID(unit) + elseif HealBot_Integrations_Nampower_Active and GetUnitGUID then + return GetUnitGUID(unit) end return nil end @@ -40,6 +42,11 @@ function HealBot_Integrations_Toggle() local spellID, caster, target, _, _, _, _, duration = arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 if not caster or not target or duration <= 0 then return end + -- The player is ALWAYS the caster in these events for Nampower + if not HealBot_PlayerGUID then + HealBot_PlayerGUID = caster + end + local spellName = GetSpellRecField(spellID, "name") if not spellName then return end @@ -76,7 +83,7 @@ function HealBot_Integrations_PruneNampower() local currentTime = GetTime() for targetName, auras in pairs(HealBot_Nampower_Auras) do -- Check if target is still in the raid/party - if not HealBot_Model:GetUnitIDByName(targetName) then + if not HealBot_Model:GetUnitByGUID(targetName) and not HealBot_Model:GetUnitIDByName(targetName) then HealBot_ReleaseTable(auras) HealBot_Nampower_Auras[targetName] = nil else @@ -127,4 +134,16 @@ end HealBot_Integrations_ClassicAPI_Active = false; HealBot_AddDebug("ClassicAPI Integration: DISABLED"); end + + if HealBot_Options_Integrations_ShowHoTTimers then + if HealBot_Integrations_Nampower_Active or HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active then + HealBot_Options_Integrations_ShowHoTTimers:Enable() + getglobal(HealBot_Options_Integrations_ShowHoTTimers:GetName().."Text"):SetTextColor(1, 1, 1) + else + HealBot_Options_Integrations_ShowHoTTimers:Disable() + getglobal(HealBot_Options_Integrations_ShowHoTTimers:GetName().."Text"):SetTextColor(0.5, 0.5, 0.5) + HealBot_Options_Integrations_ShowHoTTimers:SetChecked(0) + HealBot_Config.HealBot_Integrations_ShowHoTTimers = 0 + end + end end diff --git a/HealBot_Model.lua b/HealBot_Model.lua index 5d3511b..2b8fd20 100644 --- a/HealBot_Model.lua +++ b/HealBot_Model.lua @@ -156,7 +156,7 @@ function HealBot_Model:UpdateUnitIdentity(unit) self.units[unit].englishClass = englishClass self.units[unit].class = UnitClass(unit) - if (HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active) and HealBot_GetUnitGUID then + if (HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active or HealBot_Integrations_Nampower_Active) and HealBot_GetUnitGUID then local guid = HealBot_GetUnitGUID(unit) if guid and guid ~= "0" and guid ~= "0x0000000000000000" then self.unitGUIDs[unit] = guid @@ -185,7 +185,7 @@ end -- HealBot_Model:PreserveStateByGUID: Preserves icon and debuff state when group indices shift during combat. function HealBot_Model:PreserveStateByGUID() - if not (HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active) or not HealBot_GetUnitGUID then return end + if not (HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active or HealBot_Integrations_Nampower_Active) or not HealBot_GetUnitGUID then return end for k in pairs(pool_oldGUIDs) do pool_oldGUIDs[k] = nil end local oldGUIDs = pool_oldGUIDs diff --git a/HealBot_Options_Integrations.lua b/HealBot_Options_Integrations.lua index 41cb703..d9d754b 100644 --- a/HealBot_Options_Integrations.lua +++ b/HealBot_Options_Integrations.lua @@ -4,4 +4,5 @@ function HealBot_Options_Integrations_OnShow(this) HealBot_Options_Integrations_Nampower:SetChecked(HealBot_Config.HealBot_Integrations_Nampower); HealBot_Options_Integrations_SuperWoW:SetChecked(HealBot_Config.HealBot_Integrations_SuperWoW); HealBot_Options_Integrations_ClassicAPI:SetChecked(HealBot_Config.HealBot_Integrations_ClassicAPI); + HealBot_Options_Integrations_ShowHoTTimers:SetChecked(HealBot_Config.HealBot_Integrations_ShowHoTTimers); end diff --git a/HealBot_Options_Integrations.xml b/HealBot_Options_Integrations.xml index ab6cc20..f839c0f 100644 --- a/HealBot_Options_Integrations.xml +++ b/HealBot_Options_Integrations.xml @@ -65,7 +65,7 @@ - + @@ -80,6 +80,24 @@ + + + + + + + + + + + getglobal(this:GetName().."Text"):SetText("Track Player HoT Timers"); + + + HealBot_Config.HealBot_Integrations_ShowHoTTimers = this:GetChecked() and 1 or 0; + + + + @@ -94,6 +112,17 @@ + + + + + + + + + + + diff --git a/HealBot_View_Layout.lua b/HealBot_View_Layout.lua index f8b35b4..e206bba 100644 --- a/HealBot_View_Layout.lua +++ b/HealBot_View_Layout.lua @@ -1239,3 +1239,155 @@ function HealBot_Action_AppendNewUnits() end HealBot_ReleaseTable(unitsToCheck) end + +local HealBot_PlayerHoTsListFrame = nil +local HealBot_PlayerHoTFrames = {} + +local HealBot_PlayerHoTs_ValidHoTs = { + ["Renew"] = "Interface\\Icons\\Spell_Holy_Renew", + ["Rejuvenation"] = "Interface\\Icons\\Spell_Nature_Rejuvenation", + ["Regrowth"] = "Interface\\Icons\\Spell_Nature_ResistNature", + ["Power Word: Shield"] = "Interface\\Icons\\Spell_Holy_PowerWordShield", +} + +local HealBot_PlayerHoTs_ListCache = {} + +function HealBot_Action_UpdatePlayerHoTs() + if not HealBot_PlayerHoTsListFrame then + HealBot_PlayerHoTsListFrame = CreateFrame("Frame", "HealBot_PlayerHoTsListFrame", HealBot_Action) + HealBot_PlayerHoTsListFrame:SetWidth(128) + HealBot_PlayerHoTsListFrame:SetHeight(1) + HealBot_PlayerHoTsListFrame:SetPoint("TOPLEFT", HealBot_Action, "BOTTOMLEFT", 0, -2) + HealBot_PlayerHoTsListFrame:Show() + end + + if HealBot_Config.HealBot_Integrations_ShowHoTTimers ~= 1 then + HealBot_PlayerHoTsListFrame:Hide() + return + else + HealBot_PlayerHoTsListFrame:Show() + end + + local currentTime = GetTime() + + -- Clear cache instead of reallocating + local numHoTs = 0 + + if HealBot_Integrations_Nampower_Active and HealBot_Nampower_Auras then + for targetName, auras in pairs(HealBot_Nampower_Auras) do + for spellName, expiration in pairs(auras) do + if HealBot_PlayerHoTs_ValidHoTs[spellName] then + if expiration > currentTime then + numHoTs = numHoTs + 1 + if not HealBot_PlayerHoTs_ListCache[numHoTs] then + HealBot_PlayerHoTs_ListCache[numHoTs] = {} + end + HealBot_PlayerHoTs_ListCache[numHoTs].name = targetName + HealBot_PlayerHoTs_ListCache[numHoTs].spell = spellName + HealBot_PlayerHoTs_ListCache[numHoTs].expires = expiration + end + end + end + end + end + + -- Blank out remaining cache entries so sort doesn't read them + for i = numHoTs + 1, table.getn(HealBot_PlayerHoTs_ListCache) do + HealBot_PlayerHoTs_ListCache[i].expires = 9999999999 + end + + -- Sort only the active portion by shifting expired ones to the end + table.sort(HealBot_PlayerHoTs_ListCache, function(a, b) + return (a.expires or 0) < (b.expires or 0) + end) + + for i = 1, math.max(numHoTs, table.getn(HealBot_PlayerHoTFrames)) do + local frame = HealBot_PlayerHoTFrames[i] + if i <= numHoTs then + if not frame then + frame = CreateFrame("Frame", "HealBot_PlayerHoT" .. i, HealBot_PlayerHoTsListFrame) + frame:SetWidth(128) + frame:SetHeight(16) + if i == 1 then + frame:SetPoint("TOPLEFT", HealBot_PlayerHoTsListFrame, "TOPLEFT", 0, 0) + else + frame:SetPoint("TOPLEFT", HealBot_PlayerHoTFrames[i-1], "BOTTOMLEFT", 0, -2) + end + + local icon = frame:CreateTexture(nil, "ARTWORK") + icon:SetWidth(14) + icon:SetHeight(14) + icon:SetPoint("LEFT", frame, "LEFT", 2, 0) + frame.icon = icon + + local text = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + text:SetPoint("LEFT", icon, "RIGHT", 4, 0) + text:SetJustifyH("LEFT") + frame.text = text + + HealBot_PlayerHoTFrames[i] = frame + end + + local hot = HealBot_PlayerHoTs_ListCache[i] + local timeLeft = math.floor(hot.expires - currentTime) + + -- Resolve GUID to Name + if not HealBot_Nampower_NameCache then HealBot_Nampower_NameCache = HealBot_GetTable() end + + local dispName = HealBot_Nampower_NameCache[hot.name] + + if not dispName then + dispName = hot.name + local resolved = false + + local unitID = HealBot_Model:GetUnitByGUID(hot.name) or HealBot_Model:GetUnitIDByName(hot.name) + if unitID then + local n = UnitName(unitID) + if n then + dispName = n + resolved = true + end + elseif string.sub(hot.name, 1, 2) == "0x" or string.sub(hot.name, 1, 2) == "0X" then + if HealBot_PlayerGUID and string.lower(HealBot_PlayerGUID) == string.lower(hot.name) then + dispName = UnitName("player") or "Player" + resolved = true + elseif HealBot_GetUnitGUID then + if string.lower(HealBot_GetUnitGUID("player") or "") == string.lower(hot.name) then + dispName = UnitName("player") or "Player" + resolved = true + elseif string.lower(HealBot_GetUnitGUID("target") or "") == string.lower(hot.name) then + dispName = UnitName("target") or "Target" + resolved = true + elseif string.lower(HealBot_GetUnitGUID("mouseover") or "") == string.lower(hot.name) then + dispName = UnitName("mouseover") or "Mouseover" + resolved = true + end + end + end + + if resolved then + HealBot_Nampower_NameCache[hot.name] = dispName + elseif string.sub(hot.name, 1, 2) == "0x" then + dispName = "Unknown" + end + end + + local spellId = HealBot_GetSpellId and HealBot_GetSpellId(hot.spell) or nil + local tex = HealBot_PlayerHoTs_ValidHoTs[hot.spell] or "Interface\\Icons\\INV_Misc_QuestionMark" + if spellId then + tex = GetSpellTexture(spellId, "BOOKTYPE_SPELL") or tex + end + + frame.icon:SetTexture(tex) + frame.text:SetText(dispName .. ": " .. timeLeft .. "s") + frame.text:SetTextColor(0.8, 1, 0.8) + frame:Show() + else + if frame then + frame:Hide() + end + end + end + + HealBot_PlayerHoTsListFrame:SetHeight(math.max(1, numHoTs * 18)) +end diff --git a/README.md b/README.md index 565fa39..6103ade 100644 --- a/README.md +++ b/README.md @@ -55,12 +55,14 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ * **Performance Fix - Frame Rendering** - Eliminated redundant UI layout recalculations in `HealBot_Action_EnableButton`. The UI no longer forces `SetFont`, `SetWidth`, `SetHeight`, and `GetGlobal` lookups on every single combat tick (which previously caused 4,000+ layout updates per second in 40-man raids). * **Performance Fix - Range Checking** - Greatly reduced CPU stutter caused by range calculations. `HealBot_Range_Check` now caches map positioning math and API calls per-frame, preventing the addon from calculating the exact same `math.sqrt` distance up to 5 times per unit during heavy combat. * **Performance Fix - Unit ID Lookup** - Optimized `HealBot_FindUnitID` by pre-allocating raid unit tokens, preventing massive string concatenation and garbage collection spam during high-frequency combat events like `CHAT_MSG_ADDON` (HealComm). +* **Performance Fix - String GC Leak** - Fixed garbage collection hitches caused by `string.gfind` closures in the HealComm parsing loop by rewriting it to use closure-free `string.find` native loops. * **Debug Output Command** - Added `/hb debug` (or `/hb dbg`) slash command to toggle real-time debug message printing directly into the standard chat frame without requiring custom chat channels. * **Performance Fix - Spell Lookup** - Fixed a massive CPU drain and garbage generation issue where the entire spellbook was linearly scanned every frame per unit. Spell IDs are now cached via O(1) dictionary lookup on login and talent/spell changes. * **Performance Fix - Unit Hash Lookup** - Eliminated heavy C-API calls to `UnitName()` during incoming heals by caching raid and party unit tokens in a fast O(1) hash table that updates on roster changes. * **Performance Fix - Range Cache GC Leak** - Fixed a continuous memory leak in the range checker caused by string concatenation (`unit .. "_" .. range`) running every frame. Range states are now stored in a nested numeric table cache. * **Performance Fix - Talent Rank Scan** - Fixed severe CPU stutter when gaining or losing auras that affect stats. Previously, inventory updates forced a full equipment and spell recalculation which linearly scanned the entire talent tree multiple times per rank. Talent ranks are now cached natively. * **Integrations Bug Fix** - Removed stray translation text from `HealBot_Integrations.lua` that caused Lua syntax errors on load. +* **Feature - HoT Tracking** - Added an option in the Integrations tab to track Player-cast HoT timers in a dedicated list below the main HealBot frame. Requires Nampower, SuperWoW, or ClassicAPI to fetch exact durations. **v1.7.3** * **Performance Fix - Table Pooling Part 2** - Fixed remaining un-pooled table allocations in layout updates, macro parsing, nampower integration, and aura tracking by utilizing HealBot_GetTable(). From 6acfa36d8deb1a24d6db87d6d9836a6a4c6a270d Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Sun, 4 Oct 2026 21:16:14 +0200 Subject: [PATCH 09/10] docs: update README with recent HoT tracking, bug fixes, and performance improvements --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6103ade..6eedde0 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,11 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ * **Performance Fix - Range Cache GC Leak** - Fixed a continuous memory leak in the range checker caused by string concatenation (`unit .. "_" .. range`) running every frame. Range states are now stored in a nested numeric table cache. * **Performance Fix - Talent Rank Scan** - Fixed severe CPU stutter when gaining or losing auras that affect stats. Previously, inventory updates forced a full equipment and spell recalculation which linearly scanned the entire talent tree multiple times per rank. Talent ranks are now cached natively. * **Integrations Bug Fix** - Removed stray translation text from `HealBot_Integrations.lua` that caused Lua syntax errors on load. -* **Feature - HoT Tracking** - Added an option in the Integrations tab to track Player-cast HoT timers in a dedicated list below the main HealBot frame. Requires Nampower, SuperWoW, or ClassicAPI to fetch exact durations. +* **Feature - HoT Tracking** - Added an option in the Integrations tab to track Player-cast HoT timers in a dedicated list below the main HealBot frame. Requires Nampower, SuperWoW, or ClassicAPI to fetch exact durations. Added support for tracking Power Word: Shield. +* **Performance Fix - HoT Tracker GC Leak** - Fixed a massive Lua garbage collection memory leak in the HoT tracker that spawned hundreds of tables per second during `OnUpdate`. Converted the tracker loop to utilize a pre-allocated static cache array. +* **Bug Fix - HoT Tracker GUID Resolution** - Fixed an issue where the HoT tracker displayed "Unknown" or the raw GUID when casting on non-party members or dropping your target. The tracker now hooks `UNIT_AURA` events to dynamically build a permanent GUID-to-Name cache map. +* **Bug Fix - Player GUID Tracking** - Implemented precise player GUID trapping that caches the player's exact GUID globally upon their first aura cast. This completely eliminates self-cast tracking fallbacks and natively resolves self-targeted HoT names instantly. +* **Bug Fix - Options UI Spacing** - Fixed inverted XML `y` offset variables in `HealBot_Options_Integrations.xml` that caused overlapping checkboxes and missing text when navigating the Integrations tab. **v1.7.3** * **Performance Fix - Table Pooling Part 2** - Fixed remaining un-pooled table allocations in layout updates, macro parsing, nampower integration, and aura tracking by utilizing HealBot_GetTable(). From b3e18bfd6441c849f853ecf2f2d1afa2aae36b81 Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:49:30 +0200 Subject: [PATCH 10/10] docs: update README with HoT timer tracking details and changelog refinements --- README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6eedde0..d98adba 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ * **Talent-Based Calculations & Equipment Bonus:** Dynamic spell healing calculations for Druid, Priest, and Paladin based on talents, and automatically scans equipped gear to scale healing predictions. * **Modifier-Aware Tooltips:** Tooltips dynamically update to show exact bound actions (Shift/Ctrl/Alt) and required mana costs (turns red if insufficient mana). * **Blizzard Party Frame Toggle:** Toggle to automatically hide Blizzard's default party frames when in a group in favor of HealBot's layouts. -* **HoT & Buff Tracking:** Intelligently track active HoTs and buffs directly on the grid frames with custom icons (e.g., Renew, Rejuvenation, Regrowth, Fear Ward). +* **HoT & Buff Tracking:** Intelligently track active HoTs and buffs directly on the grid frames with custom icons (e.g., Renew, Rejuvenation, Regrowth, Fear Ward). Optional extended HoT timer tracker togglable below frames. * **Mana Bars for Healers:** Enable and position mini-mana status bars next to unit frames, toggleable to display for healer classes only or all classes. * **Curse & Debuff Warning (CDC):** Dynamic visual and audio alerts for cleanable debuffs. Customizable colors based on debuff type (Curse, Poison, Disease, Magic). * **Pet & Familiar Frames:** Dedicated, toggleable frames for tracking and healing player pets. @@ -51,7 +51,7 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ ### Change Log **v1.7.4** -* **Performance Fix - CPU Spikes** - Fixed massive FPS drops (stuttering to 8-16 FPS) during raid combat. The `HealBot_CheckBuffs` function previously forced the client to generate hidden UI tooltips for up to 32 buffs per player on every single `UNIT_AURA` event. Tooltip parsing is now cached per texture, bypassing thousands of redundant API calls per second. +* **Performance Fix - CPU Spikes** - Fixed massive FPS drops during raid combat. The `HealBot_CheckBuffs` function previously forced the client to generate hidden UI tooltips for up to 32 buffs per player on every single `UNIT_AURA` event. Tooltip parsing is now cached per texture, bypassing thousands of redundant API calls per second. * **Performance Fix - Frame Rendering** - Eliminated redundant UI layout recalculations in `HealBot_Action_EnableButton`. The UI no longer forces `SetFont`, `SetWidth`, `SetHeight`, and `GetGlobal` lookups on every single combat tick (which previously caused 4,000+ layout updates per second in 40-man raids). * **Performance Fix - Range Checking** - Greatly reduced CPU stutter caused by range calculations. `HealBot_Range_Check` now caches map positioning math and API calls per-frame, preventing the addon from calculating the exact same `math.sqrt` distance up to 5 times per unit during heavy combat. * **Performance Fix - Unit ID Lookup** - Optimized `HealBot_FindUnitID` by pre-allocating raid unit tokens, preventing massive string concatenation and garbage collection spam during high-frequency combat events like `CHAT_MSG_ADDON` (HealComm). @@ -61,12 +61,8 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ * **Performance Fix - Unit Hash Lookup** - Eliminated heavy C-API calls to `UnitName()` during incoming heals by caching raid and party unit tokens in a fast O(1) hash table that updates on roster changes. * **Performance Fix - Range Cache GC Leak** - Fixed a continuous memory leak in the range checker caused by string concatenation (`unit .. "_" .. range`) running every frame. Range states are now stored in a nested numeric table cache. * **Performance Fix - Talent Rank Scan** - Fixed severe CPU stutter when gaining or losing auras that affect stats. Previously, inventory updates forced a full equipment and spell recalculation which linearly scanned the entire talent tree multiple times per rank. Talent ranks are now cached natively. -* **Integrations Bug Fix** - Removed stray translation text from `HealBot_Integrations.lua` that caused Lua syntax errors on load. -* **Feature - HoT Tracking** - Added an option in the Integrations tab to track Player-cast HoT timers in a dedicated list below the main HealBot frame. Requires Nampower, SuperWoW, or ClassicAPI to fetch exact durations. Added support for tracking Power Word: Shield. -* **Performance Fix - HoT Tracker GC Leak** - Fixed a massive Lua garbage collection memory leak in the HoT tracker that spawned hundreds of tables per second during `OnUpdate`. Converted the tracker loop to utilize a pre-allocated static cache array. -* **Bug Fix - HoT Tracker GUID Resolution** - Fixed an issue where the HoT tracker displayed "Unknown" or the raw GUID when casting on non-party members or dropping your target. The tracker now hooks `UNIT_AURA` events to dynamically build a permanent GUID-to-Name cache map. -* **Bug Fix - Player GUID Tracking** - Implemented precise player GUID trapping that caches the player's exact GUID globally upon their first aura cast. This completely eliminates self-cast tracking fallbacks and natively resolves self-targeted HoT names instantly. -* **Bug Fix - Options UI Spacing** - Fixed inverted XML `y` offset variables in `HealBot_Options_Integrations.xml` that caused overlapping checkboxes and missing text when navigating the Integrations tab. +* **Feature - HoT Tracking** - Added an option in the Integrations tab to track Player-cast HoT timers in a dedicated list below the main HealBot frame. Requires Nampower, SuperWoW, or ClassicAPI to fetch exact durations. Added support for tracking Power Word: Shield. Implemented with a pre-allocated static cache array for zero GC leaks, and precise GUID trapping for instant name resolution on self and non-party members. + **v1.7.3** * **Performance Fix - Table Pooling Part 2** - Fixed remaining un-pooled table allocations in layout updates, macro parsing, nampower integration, and aura tracking by utilizing HealBot_GetTable().