unitframes: compare indicator auras without case folding

The indicator scan lowercased the aura's name and icon on every aura of every
unit, and SetupBuffIndicators lowercased the other side to match. Both sides
come out of the same DBC records byte for byte, so the comparison already held
without any of it: the aura's name and icon are Spell.dbc's localized name and
SpellIcon.dbc's path (aura/Data.cpp fills one struct and emits it as either the
AuraData table or the positional UnitAura tuple), and the indicator record
reads those same two fields through C_Spell.GetSpellName / GetSpellTexture.

So this was two string allocations per aura per scan to reach a result it
already had -- and on Lua 5.0 that is an allocate-and-hash each time, since the
VM interns even when the string exists. RefreshUnit runs this for every unit
frame, which in a raid is 40 frames against up to 32 auras each.

Checked every rank of all 65 indicator spells in Spell.dbc: name and icon are
byte-identical across a spell's whole ladder, no case or spelling drift, so
nothing depended on the fold.

It also drops a crash path. aura/Data.cpp yields nil for the icon when a
spell's SpellIconID is 0, and icon:lower() would have errored on that; name was
guarded on the line above but icon never was. A raw compare is just false.

The equality is now load-bearing, so AddIndicator's comment says to feed it
spell ids and never a hand-written name or icon path -- a literal
"interface\icons\foo" in that table would silently match nothing.
This commit is contained in:
Brues
2026-09-08 16:35:31 -05:00
parent e59158764c
commit 83f89be14a
+8 -4
View File
@@ -48,6 +48,12 @@ end, true)
-- Every rank of a spell carries the same name and icon, so one id per buff
-- covers the whole rank ladder. Ids missing from this client resolve to nil and
-- drop out of the list. 'predict' names the libpredict key of a HoT.
--
-- Both fields are stored raw, and RefreshUnit compares them raw. The aura's
-- name and icon and these come out of the same DBC records byte for byte --
-- Spell.dbc's localized name, SpellIcon.dbc's path -- so case folding either
-- side would only burn a string per aura per scan. Feed this ids, never
-- hand-written names or icon paths, or that equality quietly stops holding.
local indicator_cache = {}
local function AddIndicator(indicators, spellId, predict)
local record = indicator_cache[spellId]
@@ -55,7 +61,7 @@ local function AddIndicator(indicators, spellId, predict)
local name = C_Spell.GetSpellName(spellId)
local icon = name and C_Spell.GetSpellTexture(spellId)
-- cache misses as false, so an absent spell is only looked up once
record = icon and { name = name:lower(), icon = icon:lower(), predict = predict } or false
record = icon and { name = name, icon = icon, predict = predict } or false
indicator_cache[spellId] = record
end
@@ -1969,12 +1975,10 @@ function pfUI.uf:RefreshUnit(unit, component)
for i=1,n do
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if not name then break end
local texLower = icon:lower()
local nameLower = name:lower()
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
for _, filter in pairs(unit.indicators) do
if filter.icon == texLower and filter.name == nameLower then
if filter.icon == icon and filter.name == name then
if filter.predict then
local start, duration, prediction = libpredict:GetHotDuration(unitstr, filter.predict)
pfUI.uf:AddIcon(unit, pos, icon, timeleft or prediction, count, tonumber(start), tonumber(duration))