fix(damagemeters): cancel a staggered window build that a rebuild supersedes - #1254
Open
paulbuechner wants to merge 1 commit into
Open
Conversation
…ersedes The login build creates windows one per frame (CreateNextWindow re-arms itself through C_Timer.After(0)), while _EDM_Apply() tears everything down and rebuilds synchronously. When an apply lands during login the two overlap: the staggered build's remaining steps assign _windows[i] over the entries the rebuild just created. Those window tables are dropped from _windows, but their frames stay parented and shown, so the user is left with a duplicate meter that never updates. It reads as a ghost at combat start, when the live windows begin refreshing and the orphan sits there frozen. This does not need a third-party addon to trigger. EllesmereUI_Profiles.lua and EllesmereUI_SpecOverrides.lua both call _EDM_Apply() directly, so a profile apply or a spec override arriving while the login build is still in flight is enough. A build generation counter closes it. CreateNextWindow captures the generation at the start of its build and drops out if a newer one has taken over, and _EDM_Apply bumps the counter before tearing down, which supersedes any build still running. Because a superseded build can now be cut off before it reaches its closing steps, _EDM_Apply performs them itself: EnsureTooltipFrame() and the in-combat ticker restart, which the staggered build otherwise does after its last window. No added cost: one integer compare per staggered step, and the counter is only written when a build starts or is superseded. Behaviour is unchanged when the two never overlap, which is the normal login.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DISCLAIMER: AI was used to create this PR (Claude Fable 5 Max)
What does this PR do?
Fixes duplicate damage meter windows that appear after login and show up as
frozen "ghost" meters at combat start.
The login build creates windows one per frame:
CreateNextWindowre-arms itselfthrough
C_Timer.After(0)._EDM_Apply()does the opposite, tearing everythingdown and rebuilding synchronously. When an apply lands while the login build is
still in flight, the two overlap: the staggered build's remaining steps assign
_windows[i]over the entries the rebuild has just created. Those window tablesare dropped from
_windows, but their frames stay parented and shown. The useris left with a second meter that never updates, which becomes obvious at combat
start when the live windows begin refreshing and the orphan sits there frozen.
This does not need a third-party addon to trigger.
EllesmereUI_Profiles.luaandEllesmereUI_SpecOverrides.luaboth call_EDM_Apply()directly, so a profileapply or a spec override arriving during login is enough.
The fix is a build generation counter.
CreateNextWindowcaptures the generationwhen its build starts and drops out if a newer one has taken over;
_EDM_Applybumps the counter before tearing down, superseding any build still running.
Because a superseded build can now be cut off before reaching its closing steps,
_EDM_Applyperforms them itself:EnsureTooltipFrame()and the in-combatticker restart, which the staggered build otherwise does after its last window.
Nothing changes when the two never overlap, which is the normal login.
How this was discovered
It surfaced as duplicate meters while running NaowhUI's EllesmereUI profile
addon, so the first assumption was that it belonged downstream: NaowhUI writes
profile keys during login and each write triggered a meter rebuild, which is
exactly the apply-lands-mid-build case. I worked around it on that side by
deferring the rebuild.
NaowhUI then shipped its own guard in 1.0.6 (
NaowhUI.RefreshDamageMeter()inNaowhUI_Core.lua), better than the workaround: it debounces the writes so onetoggle drives a single rebuild, and it polls for the last window's globally named
frame instead of guessing a delay, capped at about 5s.
The duplicates still came back at combat start, which is what pointed at
EllesmereUI. A downstream guard can only cover its own trigger;
_EDM_Apply()iscalled directly from
EllesmereUI_Profiles.luaandEllesmereUI_SpecOverrides.luawith no equivalent protection, so the race stays reachable from inside
EllesmereUI regardless of what any addon does. Hence this fix here rather than
there.
Worth reading if you want a second account of the same race: the comment above
RefreshDamageMeterinNaowhUI_Core.lua(~line 311) describes itindependently, including a consequence I had not traced. They report that the
stranded window's close button removes the live window's saved slot, so the
window is, in their words, "gone for good on the next reload". If that holds,
this is a data-loss bug and not only a cosmetic one.
Two notes for anyone reproducing or writing similar guards:
EllesmereUIDMFrame1..5globals are not a reliable guide to which windowsare live.
CreateFramewith an existing name does not rebind the global, soafter the first build those globals stay non-nil even for frames that have been
torn down, while the live windows exist only in the private
_windowstable.Diagnostics keyed off the globals will report the wrong frames, and a readiness
probe keyed off them can read "built" while a build is still in flight. With
the generation counter in place, downstream addons should not need such a probe
at all.
_EDM_Apply()from itsown hook, tracking orphans by reference (it cannot see frames leaked before its
own baseline), or hiding/reparenting frames found through those globals, which
tears down the working meters instead.
How was it tested?
Live retail,
Interface: 120001./reload, and a frozen ghostmeter reappearing at combat start.
/reloadcycles, profile switches and spec-overrideswaps, entering and leaving combat each time. No duplicate windows, and
#_windowsmatcheswindowCountafter each apply.a rebuild supersedes the login build (the two closing steps moved into
_EDM_Apply).addon installed and actively rebuilding the meter.
Screenshots
Checklist
HookScript/hooksecurefunconly, neverSetScripton Blizzard frames - N/A, touches only EllesmereUI's own window tables