Repository navigation
perf(render): park the render loop while nothing consumes frames #445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
a6cb03b
feat(core): signal output demand from bus lanes, holds, and events
hyperb1iss b3fc8b4
feat(core): let effect renderers park and report warming
hyperb1iss e3cd0d4
feat(daemon): park the render loop while nothing consumes frames
hyperb1iss 0ab6828
feat(cli): explain a parked render loop in hypercolor status
hyperb1iss 87e806a
docs: describe how the render loop parks without output consumers
hyperb1iss add0f78
chore(python): regenerate the client for render output demand
hyperb1iss 74524e3
feat(daemon): record every thread panic through a process-wide hook
hyperb1iss 3d64f80
feat(daemon): count and log uncaptured wgpu errors instead of panicking
hyperb1iss 62598c1
feat(daemon): supervise the render pipeline and restart it in-process
hyperb1iss 1c4249b
feat(daemon): exit nonzero when the render thread cannot recover
hyperb1iss febfb79
feat(api): report render thread health in diagnose and status
hyperb1iss 4b8f617
feat(core): hold render time and skip wake work while a loop is parked
hyperb1iss bb20c6c
fix(daemon): settle transitions and media before parking, exact telem…
hyperb1iss 89a30b0
docs: correct how a parked render loop wakes and keeps time
hyperb1iss ef27ce5
feat(diagnose): name what paused the render loop in the render checks
hyperb1iss 68e0ffc
docs(render): point the parked-loop status note at /api/v1/system
hyperb1iss 5063a4d
fix(render): publish a restart only together with the failure behind it
hyperb1iss 340dd2d
refactor(diagnose): name the output power read output_power_snapshot
hyperb1iss 95200ae
Merge origin/main into nova/render-thread-supervision
hyperb1iss 0a1e57c
Merge nova/render-thread-supervision into nova/render-demand-gate
hyperb1iss 967a736
fix(render): never start a pipeline inside a park it did not enter
hyperb1iss 6854715
fix(diagnose): measure a render stall from when frames were due again
hyperb1iss b561027
fix(render): clear park telemetry whenever a pipeline starts
hyperb1iss c0947cf
fix(cli): show a parked render loop as parked, not as a failing rate
hyperb1iss 74d06c4
docs(render): say a woken loop resumes at its current tier
hyperb1iss 2a978e7
fix(panic): start backtrace excerpts at the panicking frame on Windows
hyperb1iss dd1ecd0
Merge nova/render-thread-supervision into nova/render-demand-gate
hyperb1iss 4b9ac20
Merge origin/main into nova/render-demand-gate
hyperb1iss 68359c1
fix(panic): stop the excerpt skip at the first unrecognized frame
hyperb1iss 95c2389
Merge nova/render-thread-supervision into nova/render-demand-gate
hyperb1iss f2fd310
Merge origin/main into nova/render-demand-gate
hyperb1iss d8cadbf
fix(panic): cut backtrace excerpts at std's short backtrace marker
hyperb1iss 9677c59
Merge nova/render-thread-supervision into nova/render-demand-gate
hyperb1iss 1edf3c5
Merge origin/main into nova/render-demand-gate
hyperb1iss File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| //! Output-demand signalling for the latest-value bus lanes. | ||
| //! | ||
| //! A producer that publishes onto watch lanes can stop producing while | ||
| //! nothing consumes its output, but watch channels never announce a new | ||
| //! subscriber. [`OutputDemandSignal`] closes that gap: every lane | ||
| //! subscription, every [`OutputDemandHold`], and every discrete event | ||
| //! published on the bus notifies it, so an idle producer can sleep on the | ||
| //! signal and re-evaluate the moment a consumer appears or observable state | ||
| //! changes, instead of polling for either. | ||
|
|
||
| use std::sync::Arc; | ||
| use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering, fence}; | ||
|
|
||
| use tokio::sync::Notify; | ||
| use tokio::sync::futures::Notified; | ||
|
|
||
| /// Wake signal for producers that idle while nothing consumes their output. | ||
| /// | ||
| /// Every event publication notifies it, so notification skips the waiter | ||
| /// list entirely unless a producer has armed a wait. | ||
| #[derive(Debug, Default)] | ||
| pub struct OutputDemandSignal { | ||
| epoch: AtomicU64, | ||
| holds: AtomicUsize, | ||
| armed: AtomicUsize, | ||
| notify: Notify, | ||
| } | ||
|
|
||
| impl OutputDemandSignal { | ||
| /// Monotonic count of notifications, for observing that one happened. | ||
| #[must_use] | ||
| pub fn epoch(&self) -> u64 { | ||
| self.epoch.load(Ordering::Acquire) | ||
| } | ||
|
|
||
| /// Number of live [`OutputDemandHold`]s. | ||
| #[must_use] | ||
| pub fn hold_count(&self) -> usize { | ||
| self.holds.load(Ordering::Acquire) | ||
| } | ||
|
|
||
| /// Arm a wait. Notifications reach waiters only while at least one | ||
| /// [`ArmedOutputDemand`] lives, so arm before re-checking demand. | ||
| pub fn arm(&self) -> ArmedOutputDemand<'_> { | ||
| self.armed.fetch_add(1, Ordering::SeqCst); | ||
| // Orders the arm before every demand read the waiter makes next. | ||
| fence(Ordering::SeqCst); | ||
| ArmedOutputDemand { signal: self } | ||
| } | ||
|
|
||
| /// Wake every current waiter. | ||
| pub fn notify(&self) { | ||
| self.epoch.fetch_add(1, Ordering::AcqRel); | ||
| // The state change that prompted this call (a new receiver, a hold, | ||
| // an event) may use weaker orderings, so a full fence orders it | ||
| // before the armed check. Paired with the fence in `arm`, either | ||
| // this load sees the waiter and wakes it, or the waiter's demand | ||
| // read sees the state change. | ||
| fence(Ordering::SeqCst); | ||
| if self.armed.load(Ordering::SeqCst) > 0 { | ||
| self.notify.notify_waiters(); | ||
| } | ||
| } | ||
|
|
||
| pub(super) fn acquire_hold(self: &Arc<Self>) -> OutputDemandHold { | ||
| self.holds.fetch_add(1, Ordering::AcqRel); | ||
| self.notify(); | ||
| OutputDemandHold { | ||
| signal: Arc::clone(self), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// An armed wait on an [`OutputDemandSignal`]. | ||
| #[derive(Debug)] | ||
| pub struct ArmedOutputDemand<'a> { | ||
| signal: &'a OutputDemandSignal, | ||
| } | ||
|
|
||
| impl ArmedOutputDemand<'_> { | ||
| /// Future that resolves on the next notification. | ||
| /// | ||
| /// Pin it and call [`Notified::enable`] before re-checking demand: a | ||
| /// notification that lands between the check and the await then still | ||
| /// wakes the waiter. | ||
| pub fn notified(&self) -> Notified<'_> { | ||
| self.signal.notify.notified() | ||
| } | ||
| } | ||
|
|
||
| impl Drop for ArmedOutputDemand<'_> { | ||
| fn drop(&mut self) { | ||
| self.signal.armed.fetch_sub(1, Ordering::SeqCst); | ||
| } | ||
| } | ||
|
|
||
| /// Keeps output demand registered for consumers that read the bus without | ||
| /// holding a lane receiver, such as a borrow of a per-zone sender. | ||
| /// | ||
| /// Dropping the hold withdraws the demand. Release needs no notification: | ||
| /// a producer only loses demand on its own next evaluation. | ||
| #[derive(Debug)] | ||
| #[must_use = "dropping the hold withdraws the output demand it registers"] | ||
| pub struct OutputDemandHold { | ||
| signal: Arc<OutputDemandSignal>, | ||
| } | ||
|
|
||
| impl Drop for OutputDemandHold { | ||
| fn drop(&mut self) { | ||
| let previous = self.signal.holds.fetch_sub(1, Ordering::AcqRel); | ||
| debug_assert!(previous > 0, "output demand hold underflow"); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Do not report a parked loop as an FPS failure.
When
output_demand.parkedis true, the daemon reports zeroactual_fps. This branch adds an explanation, but the render line above still labels zero FPS as an error-colored0%. Show a neutral parked state instead of FPS health while parked, and assert that distinction in the new test. (raw.githubusercontent.com)🤖 Prompt for AI Agents