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
+32 -21
View File
@@ -4,6 +4,12 @@ setfenv(1, pfUI:GetEnvironment())
pfUI.uf = CreateFrame("Frame", nil, UIParent)
pfUI.uf.frames = {}
-- Reusable buffer for C_UnitAuras.GetAuraSlots slot ids (filled by
-- ScanAuraSlots, api.lua). Each aura scan in RefreshUnit fills it once, then
-- reads every aura by slot id: one array walk per scan instead of one per index.
-- Scans run back to back and consume the buffer before the next refill.
local auraSlots = {}
-- ============================================================================
-- GUID-based Roster Tracking for Smart Updates
-- Only updates frames where the unit actually changed, not ALL 40 frames
@@ -1635,11 +1641,13 @@ function pfUI.uf:RefreshUnit(unit, component)
-- buffs
if unit.buffs and ( component == "all" or component == "aura" ) then
-- one GetAuraSlots enumeration per refresh, then a positional read per
-- slot id: allocates nothing and never re-walks the aura array per icon
ScanAuraSlots(unitstr, "HELPFUL", auraSlots, unit.config.bufflimit)
for i=1, unit.config.bufflimit do
if not unit.buffs[i] then break end
-- positional UnitBuff allocates nothing (vs a table per icon per refresh)
local name, icon, count, _, duration, expirationTime, _, _, _, spellId = C_UnitAuras.UnitBuff(unitstr, i)
local name, icon, count, _, duration, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if name then
unit.buffs[i].texture:SetTexture(icon)
@@ -1723,6 +1731,13 @@ function pfUI.uf:RefreshUnit(unit, component)
reposition = true
end
-- selfdebuff narrows to player-cast harmful auras via the PLAYER filter.
-- Player-frame debuffs aren't gated on it (it'd hide most party-applied
-- effects on you). One GetAuraSlots enumeration per refresh; the i-th slot
-- is the i-th aura of the filtered list, so `i` stays the tooltip index.
local filter = (unit.label ~= "player" and selfdebuff == "1") and "HARMFUL|PLAYER" or "HARMFUL"
ScanAuraSlots(unitstr, filter, auraSlots, unit.config.debufflimit)
for i=1, unit.config.debufflimit do
if not unit.debuffs[i] then break end
@@ -1739,12 +1754,8 @@ function pfUI.uf:RefreshUnit(unit, component)
invert_h * ((row+buffrow)*(multiply*default_border + unit.config.debuffsize + 1) + (multiply*default_border + 1)))
end
-- selfdebuff narrows to player-cast harmful auras via the PLAYER filter.
-- Player-frame debuffs aren't gated on it (it'd hide most party-applied
-- effects on you).
-- positional UnitDebuff allocates nothing; PLAYER predicate honored for selfdebuff
local filter = (unit.label ~= "player" and selfdebuff == "1") and "PLAYER" or nil
local name, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitDebuff(unitstr, i, filter)
-- positional read by slot id allocates nothing
local name, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if name then
texture, stacks, dtype = icon, count, dispelType
else
@@ -1834,8 +1845,9 @@ function pfUI.uf:RefreshUnit(unit, component)
local present = pfUI.uf.dispelPresent or {}
pfUI.uf.dispelPresent = present
for k in pairs(present) do present[k] = nil end
for i=1,16 do
local name, _, _, dispelType = C_UnitAuras.UnitDebuff(unitstr, i)
local n = ScanAuraSlots(unitstr, "HARMFUL", auraSlots)
for i=1,n do
local name, _, _, dispelType = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if name and dispelType and dispelType ~= "" then present[dispelType] = true end
end
@@ -1925,9 +1937,9 @@ function pfUI.uf:RefreshUnit(unit, component)
local pos = 1
if table.getn(unit.indicators) > 0 then
local i = 1
while true do
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitBuff(unitstr, i)
local n = ScanAuraSlots(unitstr, "HELPFUL", auraSlots)
for i=1,n do
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if not name then break end
local texLower = string.lower(icon)
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
@@ -1948,14 +1960,13 @@ function pfUI.uf:RefreshUnit(unit, component)
break
end
end
i = i + 1
end
end
if table.getn(unit.indicator_custom) > 0 then
local ai = 1
while true do
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitBuff(unitstr, ai)
local n = ScanAuraSlots(unitstr, "HELPFUL", auraSlots)
for i=1,n do
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if not name then break end
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
local lowerName = string.lower(name)
@@ -1966,12 +1977,12 @@ function pfUI.uf:RefreshUnit(unit, component)
break
end
end
ai = ai + 1
end
local debuffFilter = unit.config.selfdebuff == "1" and "PLAYER" or nil
for i=1,16 do -- scan for custom debuffs
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitDebuff(unitstr, i, debuffFilter)
local debuffFilter = unit.config.selfdebuff == "1" and "HARMFUL|PLAYER" or "HARMFUL"
n = ScanAuraSlots(unitstr, debuffFilter, auraSlots)
for i=1,n do -- scan for custom debuffs
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if name then
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
for _, filter in pairs(unit.indicator_custom) do