auras: scan through GetAuraSlots instead of by-index loops

Every aura scan loop (unit frame buffs/debuffs, dispel indicators, buff and
custom indicators, player buff frame, buffwatch bars, tooltip buff row) now
enumerates a unit's auras once with C_UnitAuras.GetAuraSlots and reads each
aura by slot id via the positional C_UnitAuras.UnitAuraBySlot. The by-index
getters re-walk the aura array from slot 0 on every call, so a per-button
loop over them was quadratic in the aura count; one enumeration plus a
by-slot read per aura is linear.

pfUI.api.ScanAuraSlots(unit, filter, buf, max) wraps GetAuraSlots' fill-a-
table form (table as the 5th argument) so no vararg Lua frame is involved:
Lua 5.0 allocates an `arg` table for every vararg call, which showed up as
nameplate OnUpdate/OnEvent memory growth in the first cut of this change.

Single by-index reads in tooltip and click handlers are unchanged (one call
each, and SetUnitAura takes the same index).

Requires the ClassicAPI build that adds GetAuraSlots' fill form; on an older
DLL the 5th argument is ignored and ScanAuraSlots would read the first slot
id as the count.
This commit is contained in:
Brues
2026-09-02 16:22:04 -05:00
parent b5025f9ec7
commit 322063eff8
6 changed files with 99 additions and 39 deletions
+16 -1
View File
@@ -7,6 +7,15 @@ pfUI:RegisterModule("buff", function ()
local br, bg, bb, ba = GetStringColor(pfUI_config.appearance.border.color)
-- Player aura slot ids, enumerated once per refresh pass (ScanPlayerAuraSlots)
-- and read per button by its aura index: one GetAuraSlots walk per range
-- instead of a by-index walk per button.
local helpfulSlots, harmfulSlots = {}, {}
local function ScanPlayerAuraSlots()
ScanAuraSlots("player", "HELPFUL", helpfulSlots, 32)
ScanAuraSlots("player", "HARMFUL", harmfulSlots, 16)
end
local function RefreshBuffButton(buff)
if buff.btype == "HELPFUL" then
if C.buffs.separateweapons == "1" then
@@ -23,7 +32,8 @@ pfUI:RegisterModule("buff", function ()
CreateBackdropShadow(buff)
end
local name, icon, count, dispelType, _, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAura("player", buff.id, buff.btype)
local slots = buff.btype == "HELPFUL" and helpfulSlots or harmfulSlots
local name, icon, count, dispelType, _, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAuraBySlot("player", slots[buff.id])
--detect weapon buffs
if buff.btype == "HELPFUL" and ((C.buffs.separateweapons == "0" and buff.gid <= pfUI.buff.wepbuffs.count) or (pfUI.buff.wepbuffs.count > 0 and buff.weapon ~= nil)) then
@@ -166,6 +176,8 @@ pfUI:RegisterModule("buff", function ()
pfUI.buff.wepbuffs.count = 0
end
ScanPlayerAuraSlots()
for i=1,32 do
RefreshBuffButton(pfUI.buff.buffs.buttons[i])
end
@@ -246,6 +258,9 @@ pfUI:RegisterModule("buff", function ()
end
end)
-- CreateBuffButton refreshes each new button from the slot buffers
ScanPlayerAuraSlots()
-- Weapon Buffs
pfUI.buff.wepbuffs = CreateFrame("Frame", "pfWepBuffFrame", UIParent)
pfUI.buff.wepbuffs.count = 0
+13 -4
View File
@@ -73,9 +73,12 @@ pfUI:RegisterModule("buffwatch", function ()
return anchor
end
local function GetBuffData(unit, id, type, selfdebuff)
local filter = (selfdebuff and type == "HARMFUL") and "HARMFUL|PLAYER" or type
local name, icon, count, dispelType, _, expirationTime = C_UnitAuras.UnitAura(unit, id, filter)
-- reusable GetAuraSlots buffer, filled once per RefreshBuffBarFrame
local auraSlots = {}
-- Reads one aura by the slot id GetAuraSlots returned (nil slot -> nil).
local function GetBuffData(unit, slot)
local name, icon, count, dispelType, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unit, slot)
if not name then return end
local remaining = expirationTime > 0 and (expirationTime - GetTime()) or 0
return remaining, icon, name, count, dispelType
@@ -234,9 +237,15 @@ pfUI:RegisterModule("buffwatch", function ()
local function RefreshBuffBarFrame(frame)
-- reinitialize all active buffs
local selfdebuff = frame.config.selfdebuff == "1"
local filter = (selfdebuff and frame.type == "HARMFUL") and "HARMFUL|PLAYER" or frame.type
-- one GetAuraSlots enumeration per refresh instead of a by-index walk per
-- bar; the i-th slot is the i-th aura of the filtered list, so `i` stays
-- the index the tooltip / cancel handlers pass to the by-index API
ScanAuraSlots(frame.unit, filter, auraSlots, 32)
for i=1,32 do
local timeleft, texture, name, stacks, dtype = GetBuffData(frame.unit, i, frame.type, selfdebuff)
local timeleft, texture, name, stacks, dtype = GetBuffData(frame.unit, auraSlots[i])
timeleft = timeleft or 0
if texture and name and name ~= "" and BuffIsVisible(frame.config, name) then
+7 -6
View File
@@ -116,6 +116,7 @@ pfUI:RegisterModule("nameplates", function ()
-- Reusable per-plate debuff display buffer (avoid GC churn from per-call table creation)
local debuffDisplayBuf = {} -- [i] = { effect, texture, stacks, dtype, duration, timeleft }
for i = 1, 16 do debuffDisplayBuf[i] = {} end
local auraSlots = {} -- reusable GetAuraSlots buffer for the per-plate aura scan
local threatMemory = {} -- guid -> true if mob had player targeted
-- local debuffSeen = {} -- reusable table for debuff tracking (avoid GC churn)
@@ -1312,11 +1313,12 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
if unitstr then
local filter = cfg.owndebuffs and "HARMFUL|PLAYER" or "HARMFUL"
local now = GetTime()
-- positional UnitAura writes straight into the reusable buffer, so the
-- per-plate scan allocates nothing (no per-aura table, no result array)
local i = 1
while debuffCount < 16 do
local aname, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitAura(unitstr, i, filter)
-- one GetAuraSlots enumeration, then positional by-slot reads straight
-- into the reusable buffer: the per-plate scan allocates nothing (no
-- per-aura table, no result array) and walks the aura array once
local n = ScanAuraSlots(unitstr, filter, auraSlots, 16)
for i = 1, n do
local aname, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if not aname then break end
debuffCount = debuffCount + 1
local b = debuffDisplayBuf[debuffCount]
@@ -1326,7 +1328,6 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
b.dtype = dispelType
b.duration = duration
b.timeleft = (expirationTime and expirationTime > 0) and (expirationTime - now) or nil
i = i + 1
end
end
for i = 1, 16 do
+11 -7
View File
@@ -189,6 +189,7 @@ pfUI:RegisterModule("tooltip", function ()
-- Icons come from a ClassicAPI object pool: ReleaseAll hides the whole
-- set each refresh, then we Acquire and re-anchor left-to-right.
local BUFF_SIZE, BUFF_SPACING, BUFF_MAX = 20, 2, 32
local auraSlots = {} -- reusable GetAuraSlots buffer for UpdateBuffs
pfUI.tooltip.buffs = CreateFrame("Frame", "pfTooltipBuffs", GameTooltip)
pfUI.tooltip.buffs:SetPoint("BOTTOMLEFT", GameTooltipStatusBar, "TOPLEFT", 0, default_border + 2)
pfUI.tooltip.buffs:SetHeight(BUFF_SIZE)
@@ -239,17 +240,20 @@ pfUI:RegisterModule("tooltip", function ()
end
local prev, count = nil, 0
for i = 1, BUFF_MAX do
local aura = C_UnitAuras.GetBuffDataByIndex(unit, i)
if not aura then break end
-- one GetAuraSlots enumeration + positional by-slot reads: no per-aura
-- table and no per-index re-walk of the aura array
local n = ScanAuraSlots(unit, "HELPFUL", auraSlots, BUFF_MAX)
for i = 1, n do
local name, texture, applications, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unit, auraSlots[i])
if not name then break end
count = count + 1
local icon = pfUI.tooltip.buffpool:Acquire()
icon.texture:SetTexture(aura.icon)
icon.stacks:SetText(aura.applications and aura.applications > 1 and aura.applications or "")
icon.texture:SetTexture(texture)
icon.stacks:SetText(applications and applications > 1 and applications or "")
icon.expirationTime = aura.expirationTime
local timeleft = aura.expirationTime and aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or 0
icon.expirationTime = expirationTime
local timeleft = expirationTime and expirationTime > 0 and (expirationTime - GetTime()) or 0
icon.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
if prev then