diff --git a/HealBotBlue.toc b/HealBotBlue.toc index 96d1a22..30b9bda 100644 --- a/HealBotBlue.toc +++ b/HealBotBlue.toc @@ -1,6 +1,6 @@ ## Interface: 11200 ## Title: HealBotBlue -## Version: 1.7.2 +## Version: 1.7.3 ## 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_Action.lua b/HealBot_Action.lua index 69ef209..207d7ad 100644 --- a/HealBot_Action.lua +++ b/HealBot_Action.lua @@ -136,9 +136,10 @@ end -- HealBot_SplitString: Internal utility: HealBot_SplitString function HealBot_SplitString(str, delimiter) - local result = {} + local result = HealBot_GetTable() if not delimiter or delimiter == "" then - return {str} + table.insert(result, str) + return result end local start_pos = 1 while true do @@ -160,6 +161,7 @@ function HealBot_RunMacroText(body) ChatFrameEditBox:SetText(commands[i]) ChatEdit_SendText(ChatFrameEditBox) end + HealBot_ReleaseTable(commands) end -- HealBot_RunMacro: Internal utility: HealBot_RunMacro diff --git a/HealBot_Controller_Aura.lua b/HealBot_Controller_Aura.lua index 3757ab8..597adee 100644 --- a/HealBot_Controller_Aura.lua +++ b/HealBot_Controller_Aura.lua @@ -41,6 +41,7 @@ function HealBot_CheckShamanWeaponBuff(spellName) if not firstWord then return false end HealBot_ScanTooltip:SetOwner(UIParent, "ANCHOR_NONE") + if not HealBot_ScanTooltip_Lines then HealBot_ScanTooltip_Lines = {} end local slots = HealBot_GetTable() if hasMainHandEnchant then table.insert(slots, 16) end if hasOffHandEnchant then table.insert(slots, 17) end @@ -49,7 +50,11 @@ function HealBot_CheckShamanWeaponBuff(spellName) HealBot_ScanTooltip:ClearLines() HealBot_ScanTooltip:SetInventoryItem("player", slot) for i = 1, HealBot_ScanTooltip:NumLines() do - local textObj = getglobal("HealBot_ScanTooltipTextLeft" .. i) + local textObj = HealBot_ScanTooltip_Lines[i] + if not textObj then + textObj = getglobal("HealBot_ScanTooltipTextLeft" .. i) + HealBot_ScanTooltip_Lines[i] = textObj + end if textObj then local text = textObj:GetText() if text and string.find(text, firstWord) then @@ -184,7 +189,7 @@ function HealBot_OnEvent_UnitAura(this, unit) local iconCount = 0 local i = 1; HealBot_UnitDebuff[unit] = nil; - local trackedTextures = {} + local trackedTextures = HealBot_GetTable() while true do local debuff, tmp, debuff_type = UnitDebuff(unit, i, 1) @@ -301,5 +306,6 @@ function HealBot_OnEvent_UnitAura(this, unit) -- Defer UI updates HealBot_View_DirtyUnits[unit] = true + HealBot_ReleaseTable(trackedTextures) end end diff --git a/HealBot_Controller_Events.lua b/HealBot_Controller_Events.lua index 7a6aab3..ab5521e 100644 --- a/HealBot_Controller_Events.lua +++ b/HealBot_Controller_Events.lua @@ -69,11 +69,11 @@ function HealBot_OnUpdate(this, arg1) local pending = HealBot_TargetRestorePending; HealBot_TargetRestorePending = nil; HealBot_TargetRestoreTimer = 0; - if pending.type == "enemy" then + if pending == "enemy" then TargetLastEnemy(); - elseif pending.type == "friend" then + elseif pending == "friend" then TargetLastTarget(); - elseif pending.type == "clear" then + elseif pending == "clear" then ClearTarget(); end end @@ -112,11 +112,11 @@ function HealBot_OnUpdate(this, arg1) -- Restore target logic if pendingCast.targetEnemy then - HealBot_TargetRestorePending = { type = "enemy" } + HealBot_TargetRestorePending = "enemy" elseif pendingCast.oldTarget and pendingCast.oldTarget ~= UnitName(pendingCast.target) then - HealBot_TargetRestorePending = { type = "friend" } + HealBot_TargetRestorePending = "friend" elseif not pendingCast.oldTarget then - HealBot_TargetRestorePending = { type = "clear" } + HealBot_TargetRestorePending = "clear" end HealBot_TargetRestoreTimer = 0 end diff --git a/HealBot_Controller_Spells.lua b/HealBot_Controller_Spells.lua index b248a76..b9bea6c 100644 --- a/HealBot_Controller_Spells.lua +++ b/HealBot_Controller_Spells.lua @@ -3,6 +3,7 @@ HealBot_CastingSpell = nil; HealBot_CastingTarget = nil; +HealBot_CastingGroupMembers = nil; HealBot_TargetRestorePending = nil; HealBot_TargetRestoreTimer = 0; @@ -158,13 +159,52 @@ function HealBot_Process_HealValue(spell, target) end local uname = UnitName(target) if uname then - HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. uname .. " <<=>> " .. HealBot_HealValue .. " << "); - HealBot_SendAddonMessage("HealComm", "Heal/" .. uname .. "/" .. HealBot_HealValue .. "/1500/"); - if not HealBot_HealsIn[uname] then - HealBot_HealsIn[uname] = 0; + if not HealBot_CastingGroupMembers then HealBot_CastingGroupMembers = {} end + for i=1, table.getn(HealBot_CastingGroupMembers) do HealBot_CastingGroupMembers[i] = nil end + table.setn(HealBot_CastingGroupMembers, 0) + if HEALBOT_PRAYER_OF_HEALING and string.find(spell, HEALBOT_PRAYER_OF_HEALING) then + if GetNumRaidMembers() > 0 then + local targetGroup = 0 + for i = 1, GetNumRaidMembers() do + local name, _, subgroup = GetRaidRosterInfo(i) + if name == uname then targetGroup = subgroup; break; end + end + if targetGroup > 0 then + for i = 1, GetNumRaidMembers() do + local name, _, subgroup = GetRaidRosterInfo(i) + if subgroup == targetGroup and name then table.insert(HealBot_CastingGroupMembers, name); end + end + end + else + table.insert(HealBot_CastingGroupMembers, UnitName("player")) + for i = 1, GetNumPartyMembers() do + if UnitName("party"..i) then table.insert(HealBot_CastingGroupMembers, UnitName("party"..i)); end + end + end + end + if table.getn(HealBot_CastingGroupMembers) == 0 then + table.insert(HealBot_CastingGroupMembers, uname) + end + + if table.getn(HealBot_CastingGroupMembers) > 1 then + local commsStr = "GrpHeal/" .. HealBot_HealValue .. "/1500/" + for _, memberName in ipairs(HealBot_CastingGroupMembers) do + commsStr = commsStr .. memberName .. "/" + end + HealBot_SendAddonMessage("HealComm", commsStr) + for _, memberName in ipairs(HealBot_CastingGroupMembers) do + HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. memberName .. " <<=>> " .. HealBot_HealValue .. " << ") + if not HealBot_HealsIn[memberName] then HealBot_HealsIn[memberName] = 0 end + HealBot_HealsIn[memberName] = HealBot_HealsIn[memberName] + HealBot_HealValue + HealBot_RecalcHeals(HealBot_FindUnitID(memberName)) + end + else + HealBot_SendAddonMessage("HealComm", "Heal/" .. uname .. "/" .. HealBot_HealValue .. "/1500/") + HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. uname .. " <<=>> " .. HealBot_HealValue .. " << ") + if not HealBot_HealsIn[uname] then HealBot_HealsIn[uname] = 0 end + HealBot_HealsIn[uname] = HealBot_HealsIn[uname] + HealBot_HealValue + HealBot_RecalcHeals(HealBot_FindUnitID(uname)) end - HealBot_HealsIn[uname] = HealBot_HealsIn[uname] + HealBot_HealValue; - HealBot_RecalcHeals(HealBot_FindUnitID(uname)); end end end @@ -217,14 +257,24 @@ function HealBot_StopCasting() if HealBot_HealValue > 0 then local uname = UnitName(HealBot_CastingTarget) if uname then - HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. uname .. " <<=>> " .. 0 - HealBot_HealValue .. " << "); - HealBot_SendAddonMessage("HealComm", "Healstop"); - if HealBot_HealsIn[uname] then - HealBot_HealsIn[uname] = HealBot_HealsIn[uname] - HealBot_HealValue; - if HealBot_HealsIn[uname] < 0 then - HealBot_HealsIn[uname] = 0; - end - HealBot_RecalcHeals(HealBot_FindUnitID(uname)); + if HealBot_CastingGroupMembers and table.getn(HealBot_CastingGroupMembers) > 1 then + HealBot_SendAddonMessage("HealComm", "GrpHealstop"); + for _, memberName in ipairs(HealBot_CastingGroupMembers) do + HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. memberName .. " <<=>> " .. 0 - HealBot_HealValue .. " << ") + if HealBot_HealsIn[memberName] then + HealBot_HealsIn[memberName] = HealBot_HealsIn[memberName] - HealBot_HealValue; + if HealBot_HealsIn[memberName] < 0 then HealBot_HealsIn[memberName] = 0; end + HealBot_RecalcHeals(HealBot_FindUnitID(memberName)); + end + end + else + HealBot_SendAddonMessage("HealComm", "Healstop"); + HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. uname .. " <<=>> " .. 0 - HealBot_HealValue .. " << "); + if HealBot_HealsIn[uname] then + HealBot_HealsIn[uname] = HealBot_HealsIn[uname] - HealBot_HealValue; + if HealBot_HealsIn[uname] < 0 then HealBot_HealsIn[uname] = 0; end + HealBot_RecalcHeals(HealBot_FindUnitID(uname)); + end end end HealBot_HealValue = 0; @@ -237,7 +287,7 @@ function HealBot_StopCasting() local ag = HealBot_Config.babortcolg[HealBot_Config.Current_Skin] or 0.1; local ab = HealBot_Config.babortcolb[HealBot_Config.Current_Skin] or 0.5; local aa = HealBot_Config.babortcola[HealBot_Config.Current_Skin] or 1; - bar.txt = getglobal(bar:GetName() .. "_text"); + if not bar.txt then bar.txt = getglobal(bar:GetName() .. "_text") end bar:SetStatusBarColor(ar, ag, ab, 0); local sr = HealBot_Config.btextdisbledcolr[HealBot_Config.Current_Skin]; local sg = HealBot_Config.btextdisbledcolg[HealBot_Config.Current_Skin]; @@ -303,7 +353,7 @@ function HealBot_CheckCasting(unit) local sg = HealBot_Config.btextenabledcolg[HealBot_Config.Current_Skin]; local sb = HealBot_Config.btextenabledcolb[HealBot_Config.Current_Skin]; local sa = HealBot_Config.btextenabledcola[HealBot_Config.Current_Skin]; - bar.txt = getglobal(bar:GetName() .. "_text"); + if not bar.txt then bar.txt = getglobal(bar:GetName() .. "_text") end bar.txt:SetTextColor(sr, sg, sb, sa); end end @@ -328,7 +378,15 @@ function HealBot_CastSpellOnFriend(spell, target) if formCancelled then -- ALWAYS put the cast into the Pending queue so the OnUpdate loop can wait for the server -- to process the unshift before attempting the cast, otherwise we get "You are in shapeshift form". - HealBot_PendingShapeshiftCast = { spell = spell, target = target, targetEnemy = targetEnemy, oldTarget = oldTarget, fireTime = GetTime() + 0.05, expires = GetTime() + 2.0 } + if not HealBot_PendingShapeshiftCast then HealBot_PendingShapeshiftCast = {} end + HealBot_PendingShapeshiftCast.spell = spell + HealBot_PendingShapeshiftCast.target = target + HealBot_PendingShapeshiftCast.targetEnemy = targetEnemy + HealBot_PendingShapeshiftCast.oldTarget = oldTarget + HealBot_PendingShapeshiftCast.fireTime = GetTime() + 0.05 + HealBot_PendingShapeshiftCast.expires = GetTime() + 2.0 + HealBot_PendingShapeshiftCast.started = nil + HealBot_PendingShapeshiftCast.nextSpam = nil return; end @@ -339,11 +397,11 @@ function HealBot_CastSpellOnFriend(spell, target) HealBot_StartCasting(spell, target, "direct"); if targetEnemy then - HealBot_TargetRestorePending = { type = "enemy" }; + HealBot_TargetRestorePending = "enemy"; elseif oldTarget and oldTarget ~= UnitName(target) then - HealBot_TargetRestorePending = { type = "friend" }; + HealBot_TargetRestorePending = "friend"; elseif not oldTarget then - HealBot_TargetRestorePending = { type = "clear" }; + HealBot_TargetRestorePending = "clear"; end HealBot_TargetRestoreTimer = 0; end @@ -472,7 +530,7 @@ function HealBot_FindHealSpells() end end); - local items = {}; + local items = HealBot_GetTable(); for bag = 0, NUM_BAG_FRAMES do for slot = 1, GetContainerNumSlots(bag) do local item = HealBot_GetItemName(bag, slot); @@ -498,6 +556,7 @@ function HealBot_FindHealSpells() HealBot_Heals["raidpet" .. i] = HealBot_Heals.party; end + HealBot_ReleaseTable(items); if HealBot_CalcEquipBonus then HealBot_AddDebug("...Done Equip Bonus:" .. RealHealing); end @@ -568,7 +627,15 @@ end -- HealBot_RecalcHeals: Flags unit for visual refresh. function HealBot_RecalcHeals(unit) - HealBot_Action_Refresh(unit); + if unit then + HealBot_View_DirtyUnits[unit] = true; + else + if HealBot_Action_UnitButtons then + for u, _ in pairs(HealBot_Action_UnitButtons) do + HealBot_View_DirtyUnits[u] = true; + end + end + end end -- HealBot_RecalcParty: Triggers group layout rebuild. @@ -673,8 +740,15 @@ function HealBot_InitGetSpellData(spell, id, class) _cast = 0; line = nil; + if not HealBot_ScanTooltip_Lines_Left then HealBot_ScanTooltip_Lines_Left = {} end + if not HealBot_ScanTooltip_Lines_Right then HealBot_ScanTooltip_Lines_Right = {} end + for lineNum = 2, 6 do - local lText = getglobal("HealBot_ScanTooltipTextLeft"..lineNum); + local lText = HealBot_ScanTooltip_Lines_Left[lineNum]; + if not lText then + lText = getglobal("HealBot_ScanTooltipTextLeft"..lineNum); + HealBot_ScanTooltip_Lines_Left[lineNum] = lText; + end if lText and lText:IsVisible() and lText:GetText() then local txt = lText:GetText(); local t1, t2, match; @@ -698,7 +772,11 @@ function HealBot_InitGetSpellData(spell, id, class) end end - local rText = getglobal("HealBot_ScanTooltipTextRight"..lineNum); + local rText = HealBot_ScanTooltip_Lines_Right[lineNum]; + if not rText then + rText = getglobal("HealBot_ScanTooltipTextRight"..lineNum); + HealBot_ScanTooltip_Lines_Right[lineNum] = rText; + end if rText and rText:IsVisible() and rText:GetText() then local txt = rText:GetText(); local t1, t2, match; diff --git a/HealBot_Integrations.lua b/HealBot_Integrations.lua index 4dd03b3..42ac6c2 100644 --- a/HealBot_Integrations.lua +++ b/HealBot_Integrations.lua @@ -44,7 +44,7 @@ function HealBot_Integrations_Toggle() if not spellName then return end if not HealBot_Nampower_Auras[target] then - HealBot_Nampower_Auras[target] = {} + HealBot_Nampower_Auras[target] = HealBot_GetTable() end local expirationTime = GetTime() + (duration / 1000) @@ -61,7 +61,8 @@ function HealBot_Integrations_Toggle() HealBot_Integrations_Nampower_Active = false; HealBot_AddDebug("nampower Integration: DISABLED"); if HealBot_Nampower_Auras then - for k in pairs(HealBot_Nampower_Auras) do + for k, v in pairs(HealBot_Nampower_Auras) do + HealBot_ReleaseTable(v) HealBot_Nampower_Auras[k] = nil end end @@ -76,6 +77,7 @@ function HealBot_Integrations_PruneNampower() 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 + HealBot_ReleaseTable(auras) HealBot_Nampower_Auras[targetName] = nil else -- Prune expired auras @@ -88,6 +90,7 @@ function HealBot_Integrations_PruneNampower() end end if not hasAuras then + HealBot_ReleaseTable(auras) HealBot_Nampower_Auras[targetName] = nil end end diff --git a/HealBot_Model.lua b/HealBot_Model.lua index 2114e4f..5d3511b 100644 --- a/HealBot_Model.lua +++ b/HealBot_Model.lua @@ -245,11 +245,12 @@ function HealBot_Model:PreserveStateByGUID() for targetUnit, stateData in pairs(stateSwaps) do -- Deep copy to prevent memory aliasing if not self.units[targetUnit] then - self.units[targetUnit] = { icons = {} } + self.units[targetUnit] = HealBot_GetTable() + self.units[targetUnit].icons = HealBot_GetTable() end local targetIcons = self.units[targetUnit].icons - if not targetIcons then targetIcons = {} end + if not targetIcons then targetIcons = HealBot_GetTable() end for k in pairs(targetIcons) do targetIcons[k] = nil end for k in pairs(self.units[targetUnit]) do self.units[targetUnit][k] = nil end @@ -262,7 +263,7 @@ function HealBot_Model:PreserveStateByGUID() self.units[targetUnit].icons = targetIcons if HealBot_UnitIcons and iconSwaps[targetUnit] then - if not HealBot_UnitIcons[targetUnit] then HealBot_UnitIcons[targetUnit] = {} end + if not HealBot_UnitIcons[targetUnit] then HealBot_UnitIcons[targetUnit] = HealBot_GetTable() end for j=1, 10 do HealBot_UnitIcons[targetUnit][j] = iconSwaps[targetUnit][j] end diff --git a/HealBot_View_Layout.lua b/HealBot_View_Layout.lua index efd88ac..25179d3 100644 --- a/HealBot_View_Layout.lua +++ b/HealBot_View_Layout.lua @@ -857,8 +857,10 @@ function HealBot_Action_PartyChanged() if i == last then break end end else - local petUnits = { "pet", "partypet1", "partypet2", "partypet3", "partypet4" }; - for _, unit in ipairs(petUnits) do + if not HealBot_PetUnits then + HealBot_PetUnits = { "pet", "partypet1", "partypet2", "partypet3", "partypet4" }; + end + for _, unit in ipairs(HealBot_PetUnits) do if not HealBot_Action_UnitButtons[unit] and HealBot_MayHeal(unit) then i = i + 1; HealBot_Action_SetHealButton(i, unit); @@ -1153,7 +1155,7 @@ end function HealBot_Action_AppendNewUnits() if not HealBot_Grid_LastI then return end - local unitsToCheck = {} + local unitsToCheck = HealBot_GetTable() -- Gather units based on config, similar to PartyChanged if HealBot_Config.GroupHeals == 1 then @@ -1220,4 +1222,5 @@ function HealBot_Action_AppendNewUnits() HealBot_Action_AppendUnit(unit) end end + HealBot_ReleaseTable(unitsToCheck) end diff --git a/README.md b/README.md index f6f3853..a0a402c 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,10 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ ### Change Log +**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. + **v1.7.2** * **Performance Fix - Table Pooling** - Fixed massive Vanilla Lua 5.0 garbage collection memory leaks caused by unbounded table allocations in high-frequency update loops (e.g., `OnUpdate` and `PreserveStateByGUID`). Moved tables to file-local scope and implemented inline clearing. * **Performance Fix - OOC Cleanup** - Added an out-of-combat garbage collection hook (`PLAYER_REGEN_ENABLED`) to purge disconnected senders from the `HealBot_IncomingHealers` global table, preventing memory bloat during prolonged play sessions.