From afdb6c3862d19af912460405e7f21527ee1c7e05 Mon Sep 17 00:00:00 2001 From: roby-brok Date: Tue, 11 Aug 2026 02:06:28 -0500 Subject: [PATCH] unitframes: stop UnitHasAggro rescanning and reallocating on every call Two costs on the hot path. It concatenated 'target' and 'targettarget' per call per unit, though pfValidUnits never changes after load -- now built once into a static triple list. And only positive results were cached, so the common case (nothing has aggro) rescanned the entire unit table on every call; negative results now cache for 0.3s, short enough that aggro still appears promptly. --- api/unitframes.lua | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/api/unitframes.lua b/api/unitframes.lua index a83ba79d..a9f3185d 100644 --- a/api/unitframes.lua +++ b/api/unitframes.lua @@ -140,32 +140,43 @@ function pfUI.api.GetUnitStats(unitstr) end local aggrodata = { } +local aggroScan = nil -- static { u, t, tt } triples, built once so the hot scan allocates nothing function pfUI.api.UnitHasAggro(unit) - -- Only cache positive results to allow instant detection when aggro changes - if aggrodata[unit] and aggrodata[unit].state > 0 and GetTime() < aggrodata[unit].check + 1 then - return aggrodata[unit].state + local now = GetTime() + local data = aggrodata[unit] + -- Cache positive results 1s (so aggro clears fast) AND negative results 0.3s + -- (so we don't rescan the whole unit table on every call while nothing has aggro). + if data and now < data.check + (data.state > 0 and 1 or 0.3) then + return data.state end - aggrodata[unit] = aggrodata[unit] or { } - aggrodata[unit].check = GetTime() - aggrodata[unit].state = 0 + if not data then data = { }; aggrodata[unit] = data end + data.check = now + data.state = 0 if UnitExists(unit) and UnitIsFriend(unit, "player") then - for u in pairs(pfValidUnits) do - local t = u .. "target" - local tt = t .. "target" - - if UnitExists(t) and UnitIsUnit(t, unit) and UnitCanAttack(u, unit) then - aggrodata[unit].state = aggrodata[unit].state + 1 + -- pfValidUnits never changes after load, so precompute the "target" / + -- "targettarget" token strings once instead of concatenating per call. + if not aggroScan then + aggroScan = {} + for u in pairs(pfValidUnits) do + local t = u .. "target" + table.insert(aggroScan, { u = u, t = t, tt = t .. "target" }) end + end - if UnitExists(tt) and UnitIsUnit(tt, unit) and UnitCanAttack(t, unit) then - aggrodata[unit].state = aggrodata[unit].state + 1 + for i = 1, table.getn(aggroScan) do + local s = aggroScan[i] + if UnitExists(s.t) and UnitIsUnit(s.t, unit) and UnitCanAttack(s.u, unit) then + data.state = data.state + 1 + end + if UnitExists(s.tt) and UnitIsUnit(s.tt, unit) and UnitCanAttack(s.t, unit) then + data.state = data.state + 1 end end end - return aggrodata[unit].state + return data.state end pfUI.uf.glow = CreateFrame("Frame", nil, UIParent)