Use zero-GC ClassicAPI debuff data

This commit is contained in:
Dusk-92
2026-08-24 16:00:19 +02:00
parent a330f424da
commit 55a3c08f80
+82 -41
View File
@@ -13,56 +13,88 @@ local module = ShaguTweaks:register({
local libdebuff = ShaguTweaks.libdebuff
local TimeConvert = ShaguTweaks.TimeConvert
local function GetTargetDebuff(index)
-- ClassicAPI exposes the real aura name, stack count, dispel type and,
-- when known by the client, the exact applied duration/expiration time.
-- Keep libdebuff as the compatibility fallback for missing timing data.
local aura = API and API.GetDebuffDataByIndex and API.GetDebuffDataByIndex("target", index)
if aura then
local duration = aura.duration and aura.duration > 0 and aura.duration or nil
local dtype = aura.dispelName and aura.dispelName ~= "" and aura.dispelName or nil
if duration and aura.expirationTime and aura.expirationTime > 0 then
local timeleft = aura.expirationTime - GetTime()
if timeleft < 0 then timeleft = 0 end
return aura.name, nil, aura.icon, aura.applications, dtype, duration, timeleft
end
-- ClassicAPI may know the aura but not its exact expiration when the cast
-- was not observed. Preserve ShaguTweaks' old estimated timer in that case.
if duration and libdebuff and libdebuff.UnitDebuff then
local effect, rank, texture, stacks, legacyType, legacyDuration, legacyTimeleft = libdebuff:UnitDebuff("target", index)
return aura.name or effect, rank, aura.icon or texture,
aura.applications or stacks, dtype or legacyType, legacyDuration, legacyTimeleft
end
-- Duration 0 means an indefinite aura: show metadata, but no cooldown.
return aura.name, nil, aura.icon, aura.applications, dtype, nil, -1
end
local function GetLegacyDebuff(index)
if libdebuff and libdebuff.UnitDebuff then
return libdebuff:UnitDebuff("target", index)
end
end
local function CreateTextCooldown(cooldown)
if cooldown.readable then return end
local function NormalizeAuraString(value)
return value and value ~= "" and value or nil
end
cooldown.readable = CreateFrame("Frame", "pfCooldownFrame", cooldown:GetParent())
local function ResolveDebuffTiming(name, icon, stacks, dtype, duration, expirationTime, index, now)
icon = NormalizeAuraString(icon)
dtype = NormalizeAuraString(dtype)
duration = duration and duration > 0 and duration or nil
if duration and expirationTime and expirationTime > 0 then
local timeleft = expirationTime - now
if timeleft < 0 then timeleft = 0 end
return name, nil, icon, stacks, dtype, duration, timeleft
end
-- ClassicAPI always knows the aura metadata, but expiration can be unknown
-- when the client did not observe its application. Keep ShaguTweaks' old
-- estimate only for that missing-timing case.
if duration then
local effect, rank, texture, legacyStacks, legacyType,
legacyDuration, legacyTimeleft = GetLegacyDebuff(index)
if effect then
return name or effect, rank, icon or texture, stacks or legacyStacks,
dtype or legacyType, legacyDuration, legacyTimeleft
end
end
-- Duration 0 is a permanent/indefinite aura. Keep its metadata visible but
-- do not create a fake cooldown.
return name, nil, icon, stacks, dtype, nil, -1
end
local function GetTargetDebuff(index, now)
-- Prefer ClassicAPI's positional accessor: same useful aura/timing data as
-- AuraData, but no temporary Lua table allocation for every debuff button.
if API and API.aurapositional and API.UnitDebuff then
local name, icon, stacks, dtype, duration, expirationTime =
API.UnitDebuff("target", index)
if name then
return ResolveDebuffTiming(name, icon, stacks, dtype, duration,
expirationTime, index, now)
end
return
end
-- Compatibility with older ClassicAPI builds that only expose AuraData.
local aura = API and API.GetDebuffDataByIndex and
API.GetDebuffDataByIndex("target", index)
if aura then
return ResolveDebuffTiming(aura.name, aura.icon, aura.applications,
aura.dispelName, aura.duration, aura.expirationTime, index, now)
end
return GetLegacyDebuff(index)
end
local function CreateTextCooldown(cooldown)
if cooldown.readable then return cooldown.readable end
-- These overlays never need global frame names. Keeping them anonymous
-- avoids collisions when several target debuffs are visible at once.
cooldown.readable = CreateFrame("Frame", nil, cooldown:GetParent())
cooldown.readable:SetAllPoints(cooldown)
cooldown.readable:SetFrameLevel(cooldown:GetParent():GetFrameLevel() + 1)
cooldown.readable.text = cooldown.readable:CreateFontString("pfCooldownFrameText", "OVERLAY")
cooldown.readable.text = cooldown.readable:CreateFontString(nil, "OVERLAY")
cooldown.readable.text:SetFont(STANDARD_TEXT_FONT, 10, "OUTLINE")
cooldown.readable.text:SetPoint("CENTER", cooldown.readable, "CENTER", 0, 0)
cooldown.readable:SetScript("OnUpdate", function()
this.elapsed = (this.elapsed or 0) + arg1
if this.elapsed < .1 then return end
this.elapsed = 0
local parent = this:GetParent()
if not parent then this:Hide() return end
if not this.next then this.next = GetTime() + .1 end
if this.next > GetTime() then return end
this.next = GetTime() + .1
-- fix own alpha value (should be inherited, but somehow isn't always)
this:SetAlpha(parent:GetAlpha())
@@ -73,6 +105,8 @@ local function CreateTextCooldown(cooldown)
this:Hide()
end
end)
return cooldown.readable
end
module.enable = function(self)
@@ -80,8 +114,13 @@ module.enable = function(self)
TargetDebuffButton_Update = function()
HookTargetDebuffButton_Update()
-- All buttons in this refresh share the same clock sample. Besides being
-- cheaper, this keeps their displayed countdowns perfectly aligned.
local now = GetTime()
for i=1, MAX_TARGET_DEBUFFS do
local effect, rank, texture, stacks, dtype, duration, timeleft = GetTargetDebuff(i)
local effect, rank, texture, stacks, dtype, duration, timeleft =
GetTargetDebuff(i, now)
local button = _G["TargetFrameDebuff"..i]
if button and not button.cd then
@@ -113,12 +152,14 @@ module.enable = function(self)
end
if button and effect and duration and timeleft and timeleft >= 0 then
local start = GetTime() + timeleft - duration
CreateTextCooldown(button.cd)
local start = now + timeleft - duration
local readable = CreateTextCooldown(button.cd)
CooldownFrame_SetTimer(button.cd, start, duration, 1)
button.cd.readable.start = start
button.cd.readable.duration = duration
button.cd.readable:Show()
readable.start = start
readable.duration = duration
readable.elapsed = 0
readable.text:SetText(TimeConvert(timeleft))
readable:Show()
button.cd:Show()
elseif button then
CooldownFrame_SetTimer(button.cd,0,0,0)