perf(aurakit): check the build budget before a job, not after it - #2010
Open
JuJuFX-dev wants to merge 1 commit into
Open
perf(aurakit): check the build budget before a job, not after it#2010JuJuFX-dev wants to merge 1 commit into
JuJuFX-dev wants to merge 1 commit into
Conversation
The build worker's budget test sat after RunJob, so a frame cost the budget PLUS one whole job. Measured on 2026-09-07 with /euiakprof during plate aura pool growth: np:debuffs at 5.869 ms fit under the 8 ms combat budget, the loop continued, np:buffs added 8.916, and the frame landed at 14.785 ms. That was 98.7 % of the addon's worst frame in a 127 s run, and euidiag cannot see it at all because it samples every 0.1 s. The queue order in QueueBundleBuild is debuffs, buffs, cc+pool, which is exactly the adjacent pair the peak frame contained. Each label now carries a running cost estimate, taken from the job's own elapsed time, and a job only starts if it still fits. Two rules keep that safe: The first job of a frame always runs, whatever it costs. np:buffs alone exceeds the 8 ms combat budget, so a strict check would leave it at the head of the queue forever. Since the flag resets every frame, any job at the head is at most one frame from running, which rules out starvation structurally rather than by argument. A label with no estimate yet counts as possibly expensive and waits for a fresh frame. Letting it through was the first version and it did not fix the cold case at all: the first occurrence of a label still produced the full 14.785 ms frame, which is the exact case this change exists for. The estimate rises to a new maximum immediately and decays at a quarter of the gap, because a cheap sample must not license a spike on the next frame. Verified before any in-game test by porting the drain loop, old and new, and replaying it with the measured job costs and the real queue order: budget 8 ms, 3 bundles old 6 frames / worst 14.785 new 12 frames / worst 8.916 budget 250 ms, 16 bundles old 2 frames / worst 251.3 new 4 frames / worst 249.2 unknown 20 ms job, 8 ms budget drains one job per frame, no starvation So the worst frame drops 40 % and is now bounded by the largest single job rather than the largest pair. The login turbo window is untouched by design. The price is throughput in the gentle regime: three bundles drain in twelve frames instead of six, so plate auras during growth land about 70 ms later at 85 fps. debugprofilestop calls did not increase. The pre-check reuses the previous iteration's timestamp and the post-check reuses the job's own, so it is one call per job where it used to be one per loop iteration. All 26 QueueBuildJob sites in the suite pass a label, so the unlabelled fallback bucket is defensive only. The entry is not consumed before the pre-check returns, so a deferred job stays at the head and cannot be dropped. Not measured in game yet, and the raid-side consumers cannot be A/B-ed with the content available here; they are covered by inspection and by the checklist instead. Shared file touched with prior sign-off: EllesmereUI_AuraKit.lua.
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.
What does this PR do?
The shared aura build queue in
EllesmereUI_AuraKit.luadrains with a per-frame time budget, but the budget was tested after each job had already run rather than before starting one. A frame could therefore cost the budget plus one entire job, and this PR removes that overshoot. No job is made slower or faster by it: the same jobs run in the same order and do the same work, they are just no longer allowed to stack past the budget within one frame.Worked through with the measured costs of the nameplate bundle jobs, against the 8 ms in-combat budget:
The 8.9 ms is what that job costs either way. What changes is whether it is allowed to land on top of a frame that is already most of the way through its budget. This matters whenever the bundle pool has to grow during a fight, and a 14.8 ms frame is a dropped one at 85 fps.
Each job label now carries a running estimate of its own cost, taken from the job's elapsed time, and a job only starts if it still fits in the remaining budget. Two rules keep that safe. The first job of a frame always runs whatever it costs, because a single job can exceed the whole combat budget and a strict check would leave it at the head of the queue forever; since the flag resets every frame, any job at the head is at most one frame from running, which rules out starvation structurally rather than by argument. A label with no estimate yet counts as possibly expensive and waits for a fresh frame, because letting it through leaves the very first occurrence of a label at the full cost, which is the exact case this change exists for.
For the player this is fewer micro-hitches when a lot of enemy nameplates appear at once. There is no new setting, no visual change, and no change to what work is done: the pre-check returns before the queue entry is consumed, so the order of jobs, the set of jobs and their content are all identical, and only the frame a job runs in can move. The old code already split jobs across frames whenever the budget was hit, so no new state is reachable for any consumer of the queue.
The trade is throughput in the gentle 8 ms regime. Three bundles drain in twelve frames instead of six, so plate auras during pool growth land roughly 70 ms later at 85 fps. The 250 ms login window is untouched by design.
How was it tested?
Tested in game on live, in a follower dungeon with the same large chained pull before and after. The addon's worst frame dropped from 14.785 ms to 6.383 ms, and the breakdown is the part that matters: before, the worst frame carried two build jobs, and after it carries exactly one, which is precisely what the pre-check specifies. Expressed as a ratio inside each run, so that run-to-run variation cannot flatter it, the worst frame went from 1.66 times the largest single job to exactly 1.00 times.
Both the nameplate and the RaidFrames consumers of the queue were exercised in play.
EUI_RaidFrames_AuraContainers.luaserves raid, party and extra unit buttons from one code path, so the party frames present throughout the test runs built and populated their aura containers through the samerf:*jobs and the same job bodies; a raid-sized group multiplies how many of those jobs are queued but adds no new one. Player Aura Bars is not affected at all, since it never touches the queue and builds through the synchronousAK.RequestContainerpath. The CooldownManager consumer was not exercised and was checked by reading instead: both of its queue sites, like every RaidFrames one, open with an existence guard that re-validates its own precondition rather than assuming a sibling job ran in the same frame.Checklist
debugprofilestopcalls per iteration did not increase, the pre-check reuses the previous iteration's timestamp, and the only added state is one small table keyed by the fixed set of job labelsHookScript/hooksecurefunconly, neverSetScripton Blizzard frames - N/A, no Blizzard frame is touched