Skip to content

Commit 98cfa55

Browse files
committed
Border: shared OnUpdate driver for border animations
Replace the per-border animDriver frame (one CreateFrame + OnUpdate each) with a single UIParent-hosted driver that ticks a registry of every active animated border. Cuts N OnUpdate scripts to one — the scaling win for enabling animated borders on rows of icons. Each registry entry keeps its own accumulated elapsed; ticks receive (border, elapsed, dt) so DF_DASH/custom overlays use elapsed and DF_PULSATE uses dt for its phase accumulator (resume-across-restart and recolour-in-place still work off the border's own _df* fields). Normal borders are skipped while hidden (preserving the old auto-hide); secretRect borders always tick, exactly as the prior UIParent driver did. StartAnimation's dedupe + StopAnimation teardown now go through the registry.
1 parent d421061 commit 98cfa55

1 file changed

Lines changed: 52 additions & 36 deletions

File tree

Frames/Border.lua

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ end
459459
-- 2. Custom OnUpdate animators — operate directly on the 4 edge textures
460460
-- by modulating SetAlpha each frame. No LCG involved. Tick functions
461461
-- live in `customTicks` below; the shared driver frame is created
462-
-- lazily via ensureDriver(border).
462+
-- lazily via the shared anim driver (registerAnimTick).
463463
-- "WIPE" sweep a bright highlight clockwise around perimeter
464464
-- "RIPPLE" all edges pulse alpha with per-edge phase offsets
465465
-- "SEGMENT_REVEAL" edges fade in sequentially top→right→bottom→left
@@ -503,13 +503,45 @@ end
503503
-- Hides it, and the aura-container teardown path calls StopAnimation on every
504504
-- slot border so a de-configured / winner-changed / rebuilt AD border leaves no
505505
-- orphaned ticking driver.
506-
local function ensureDriver(border)
507-
if border.animDriver then return border.animDriver end
508-
local driverParent = border._secretRect and UIParent or border
509-
local d = CreateFrame("Frame", nil, driverParent)
510-
d.elapsed = 0
511-
border.animDriver = d
512-
return d
506+
-- Shared OnUpdate driver: ONE UIParent-hosted frame ticks a registry of every
507+
-- animated border, instead of a CreateFrame + OnUpdate per border. secretRect
508+
-- borders (AD / aura-container slot children) MUST be driven externally — the
509+
-- button subtree's onUpdateMode="disabled" suppresses OnUpdate through every
510+
-- descendant, so a driver parented under the border there would install but never
511+
-- fire. A single shared host keeps the per-border cost to one registry entry.
512+
--
513+
-- Each entry carries its own accumulated elapsed; ticks get (border, elapsed, dt)
514+
-- — elapsed for offset/phase-from-absolute effects (custom overlays, DF_DASH), dt
515+
-- for the DF_PULSATE phase accumulator. A NORMAL border is skipped while hidden
516+
-- (preserving the old per-border driver's auto-hide); secretRect borders always
517+
-- tick (their textures' visibility rides the slot's secret show/hide, and a tick
518+
-- on a hidden texture is a harmless SetAlpha — same as the prior UIParent driver).
519+
local animRegistry = {} -- [border] = { fn = fn, elapsed = number }
520+
local sharedAnimDriver
521+
local function ensureSharedAnimDriver()
522+
if sharedAnimDriver then return sharedAnimDriver end
523+
sharedAnimDriver = CreateFrame("Frame", nil, UIParent)
524+
sharedAnimDriver:SetScript("OnUpdate", function(_, dt)
525+
for border, e in pairs(animRegistry) do
526+
if border._secretRect or border:IsShown() then
527+
e.elapsed = e.elapsed + dt
528+
e.fn(border, e.elapsed, dt)
529+
end
530+
end
531+
end)
532+
return sharedAnimDriver
533+
end
534+
-- Register/replace this border's per-frame tick. initialElapsed seeds the
535+
-- accumulator (DF_DASH resumes its march across restarts; others start at 0).
536+
local function registerAnimTick(border, fn, initialElapsed)
537+
ensureSharedAnimDriver()
538+
local e = animRegistry[border]
539+
if not e then e = {}; animRegistry[border] = e end
540+
e.fn = fn
541+
e.elapsed = initialElapsed or 0
542+
end
543+
local function unregisterAnimTick(border)
544+
animRegistry[border] = nil
513545
end
514546

515547
-- Reset all four edges to fully opaque. Called from StopAnimation so the
@@ -963,11 +995,7 @@ function Border:StopAnimation(border)
963995
if border.animRect then stopAll(border.animRect) end
964996
if border.glowExtent then stopAll(border.glowExtent) end
965997
end
966-
if border.animDriver then
967-
border.animDriver:SetScript("OnUpdate", nil)
968-
border.animDriver:Hide()
969-
border.animDriver.elapsed = 0
970-
end
998+
unregisterAnimTick(border)
971999
-- Hide all overlay sets from prior animation passes. The cornerExtras
9721000
-- field is from a previous-rev CORNERS_ONLY implementation; we keep
9731001
-- the Hide-loop for backward compat on profiles where the field was
@@ -1031,8 +1059,8 @@ local function animSpecHash(anim)
10311059
}, "|")
10321060
end
10331061

1034-
-- OnUpdate-driver effects: those whose motion is driven by border.animDriver's
1035-
-- OnUpdate (as opposed to LCG glows or the static shape modes). The dedupe in
1062+
-- OnUpdate-driver effects: those whose motion is driven by the shared anim
1063+
-- driver's OnUpdate (as opposed to LCG glows or the static shape modes). The dedupe in
10361064
-- StartAnimation verifies the driver is actually live for these before no-opping.
10371065
local DRIVER_ANIMS = { DF_DASH = true, WIPE = true, RIPPLE = true, SEGMENT_REVEAL = true, DF_PULSATE = true }
10381066

@@ -1059,9 +1087,7 @@ function Border:StartAnimation(border, spec)
10591087
-- frequency slider off 1 and back" symptom). When the driver is dead, fall
10601088
-- through and restart instead of no-opping.
10611089
if border._animHash == newHash then
1062-
local d = border.animDriver
1063-
if not DRIVER_ANIMS[border.activeAnimation]
1064-
or (d and d:IsShown() and d:GetScript("OnUpdate")) then
1090+
if not DRIVER_ANIMS[border.activeAnimation] or animRegistry[border] then
10651091
return
10661092
end
10671093
end
@@ -1171,12 +1197,8 @@ function Border:StartAnimation(border, spec)
11711197
local tick = customTicks[anim.type]
11721198
if tick then
11731199
setupAnimOverlay(border, anim)
1174-
local d = ensureDriver(border)
1175-
d.elapsed = 0
1176-
d:Show()
1177-
d:SetScript("OnUpdate", function(self, dt)
1178-
self.elapsed = (self.elapsed or 0) + dt
1179-
tick(border, anim, self.elapsed)
1200+
registerAnimTick(border, function(b, el)
1201+
tick(b, anim, el)
11801202
end)
11811203
border.activeAnimation = anim.type
11821204
return
@@ -1189,7 +1211,7 @@ function Border:StartAnimation(border, spec)
11891211
-- AD's legacy expiring border pulse; exposed as a first-class animation
11901212
-- type so it works as either a continuous Border Animation OR as the
11911213
-- value the new Expiring Animation dropdown will swap in below
1192-
-- threshold (Stage 5.1d.2+). Uses ensureDriver's OnUpdate frame; on
1214+
-- threshold (Stage 5.1d.2+). Uses the shared anim driver; on
11931215
-- StopAnimation the existing resetEdgeAlphas() restores the edges
11941216
-- back to alpha 1 so the next render is clean.
11951217
if anim.type == "DF_PULSATE" then
@@ -1208,8 +1230,6 @@ function Border:StartAnimation(border, spec)
12081230
-- path at the top of StartAnimation can change the pulse speed on the
12091231
-- already-running driver without re-SetScript'ing.
12101232
border._dfPulsatePeriod = 2 / rawFreq
1211-
local d = ensureDriver(border)
1212-
d:Show()
12131233
-- Advance a PHASE accumulator in [0,1) by dt/period each frame rather
12141234
-- than deriving phase from absolute elapsed. Two consequences:
12151235
-- * Changing the period (frequency) only changes how fast the phase
@@ -1221,7 +1241,7 @@ function Border:StartAnimation(border, spec)
12211241
-- wave = (1 - cos(2π·phase)) / 2 is a smooth 0→1→0 (full→low→full)
12221242
-- curve with zero-slope endpoints, so each cycle blends seamlessly
12231243
-- into the next with no visible seam at the loop point.
1224-
d:SetScript("OnUpdate", function(self, dt)
1244+
registerAnimTick(border, function(border, el, dt)
12251245
local p = border._dfPulsatePeriod or 2
12261246
local ph = ((border._dfPulsatePhase or 0) + dt / p) % 1
12271247
border._dfPulsatePhase = ph
@@ -1256,16 +1276,12 @@ function Border:StartAnimation(border, spec)
12561276
-- Marching: OnUpdate advances the offset, reading colour/size from
12571277
-- the fields so a live recolour is picked up next tick. elapsed
12581278
-- persists across restarts so a spec change doesn't snap the ants.
1259-
local d = ensureDriver(border)
1260-
d.elapsed = border._dfDashElapsed or 0
1261-
d:Show()
1262-
d:SetScript("OnUpdate", function(self, dt)
1263-
self.elapsed = (self.elapsed or 0) + dt
1264-
border._dfDashElapsed = self.elapsed
1265-
local offset = (self.elapsed * marchSpeed) % DF_DASH_PATTERN
1279+
registerAnimTick(border, function(border, el)
1280+
border._dfDashElapsed = el
1281+
local offset = (el * marchSpeed) % DF_DASH_PATTERN
12661282
drawDashes(border, offset, border._dfDashTh, border._dfDashInset,
12671283
border._dfDashR, border._dfDashG, border._dfDashB, border._dfDashA)
1268-
end)
1284+
end, border._dfDashElapsed or 0)
12691285
else
12701286
-- Static: draw once, no driver (cheaper).
12711287
drawDashes(border, 0, border._dfDashTh, border._dfDashInset, r, g, b, a)

0 commit comments

Comments
 (0)