Render through the scheduler, without the runloop, RSVP, or backburner (RFC 957 end state) - #21655
Draft
NullVoxPopuli-ai-agent wants to merge 2 commits into
Draft
NullVoxPopuli-ai-agent wants to merge 2 commits into
NullVoxPopuli-ai-agent wants to merge 2 commits into
Conversation
Adds the `@ember/scheduler` package proposed by RFC 0957:
- `render`, `layout`, `composite`, `next` and `idle` phase functions,
each returning a promise that resolves according to the registered
scheduling strategy
- `registerStrategy`, for providing the scheduling strategy when defining
the Application
- `@ember/scheduler/strategy`, the default strategy implementation, which
flushes the render/layout/composite phases in order via ordered
requestAnimationFrame callbacks within a single frame, prior to paint
The deprecations of @ember/runloop and RSVP described by the RFC are left
to follow-up work; this is the additive API surface.
Fix Safari flake and idle() starvation in the default strategy
The "composite while composite is flushing" test asserted ordering across
two independent channels: a setTimeout task scheduled during frame 1
versus frame 2's requestAnimationFrame callbacks. The HTML spec does not
order pending timer tasks against the next rendering opportunity, and
Safari 15.6 runs the next frame's rAF callbacks first. The test now
anchors entirely to the rAF channel, using a raw requestAnimationFrame
registered ahead of the rescheduled phase windows as the frame-2
boundary.
idle() also armed requestIdleCallback without a timeout; fully-idle or
backgrounded pages can starve rIC indefinitely, leaving the promise
unresolvable. Cap the wait with { timeout: 500 }.
This is the RFC 957 end state on top of the scheduler interface (the previous commit, from emberjs#21552). Tag invalidation tells the renderer's scheduler directly. Nothing in the framework schedules through @ember/runloop, and backburner and RSVP are gone. - The default @ember/scheduler strategy is the renderer's clock: one render per tick, with bounded settle rounds, and a microtask, animation-frame, or task leg chosen by a microtask-window tick classifier. - @ember/runloop is a compatibility layer without dependencies: run, join, and bind are plain calls, queues become microtasks, and timers are native timers. Its public types are the same as on main. - Native promises replace RSVP in the router, the route managers, PromiseProxyMixin, and the tests. - Render settledness is reported as edges (isRenderPending on @ember/renderer), for test waiters. - Destroys drain synchronously when an engine instance is destroyed. This commit holds only the scheduler parts of the closed spike emberjs#21520. Its VM and allocation changes, and the "notify once per tick" latch, are left out, so that each one can be measured on its own. Known gap: the browser suite stops early. Tests that set state in runTask and then assert the DOM in the same task expect a synchronous render. The first ones are in the debug render tree tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This branch has not been deployed
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.
Renders through
@ember/schedulerwith no runloop, RSVP, or backburner in the framework. This is the RFC 957 end state, and only that: it is the scheduler part of the closed spike #21520, rebuilt on currentmain, so it can be measured on its own.Supersedes the scheduler half of #21520 and #21519. Implements (end state of) emberjs/rfcs#957.
Commits
f3ce59665dis Implement RFC #957: Render Aware Scheduler Interface #21552's interface commit, rebased ontomain(the same commit as in Spike theuse-async-scheduleroptional feature from RFC 957 #21554).eb9b76b539hooks the renderer into the scheduler and removes the runloop, RSVP, and backburner. The commit message lists what changed.The spike's VM and allocation changes, and its "notify once per tick" latch, are not in this PR. #21656 has them, on top of this branch.
Benchmark
On the rere-benchmark ember apps: headless at 60 fps, 8x CPU throttle, and no profiler. Chrome 154 runs on 3 CPUs of a VM.
main, this PR, and the two builds of #21656 ran in 4 mirrored cycles: 8 runs for each build, with 4 samples per bench in each run. Each number is the median of 32 samples. The first 4mainruns and the last 4 differ by 1.2% (geometric mean).Times are medians in ms, and negative is faster. The geometric mean is -50%.
main, backburner starts an autorun for everyawait. In a CPU profile, the async benches spend 76% of their time inside its autorun end. Here they render once per tick.setnow walks the notify chain. On1 item, 100k updates,scheduleRevalidatetakes 15 ms and the renderer callback 12 ms, where backburner'sensureInstancetook 1.6 ms. All rendering optimizations, measured again on top of the scheduler #21656 adds a latch that notifies once per tick, and measures it on top of this PR.mainrenders after each burst. This PR does too, unless Chrome holds frames back, and then several bursts become one render. The samples of this PR range from 116 to 1201 ms.Machine
Checks
tsc --noEmit, the type tests (stable and stable-decorators),pnpm build, ESLint with--report-unused-disable-directives, and Prettier pass.@ember/runloopmatchmain. The type tests changed only where this PR changes the API:_backburneris gone,Transitionreturns native promises, and the default strategy isRenderClockStrategy.runTaskand then assert the DOM in the same task expect a synchronous render, starting with the debug render tree tests. The spike SPIKE: every rendering-performance lever combined (RFC 957 end state + VM optimizations) #21520 stopped at the same place.🤖 Generated with Claude Code