fix: Run mode behaviour (loop_once / loop_ntimes) - #1084
Merged
Conversation
Reintroduce the pre-0.20 `LoopMode::loop_once()` / `NTimes` behaviour, lost in the Bevy port, as a true single-view mode. `RunMode::loop_once()` / `loop_ntimes(n)` run `update` and `view` exactly `n` times, then hold the last frame on screen while the window idles at ~0 CPU and stays closable and resizable - ideal for static `sketch`-based compositions. Because `view` runs a fixed number of times rather than continuously, a sketch reading live time or input is genuinely frozen. Holding a static frame is not free under Bevy: once a window stops drawing, the camera's intermediate render texture is no longer kept populated, so simply skipping `view` presents a black window. Instead the draw is frozen. A new `nannou_draw::DrawFrozen` resource gates the per-frame draw systems (`reset_draw`, `clear_previous_frame`, `update_draw_mesh`) so the last frame's meshes and clear color persist and the render graph keeps drawing them. `apply_loop_once` (a `Last` system) counts rendered frames and, once the budget is spent, freezes the draw and switches to `freeze()` to idle. It runs in `Last` so the final frame's meshes, spawned in `PostUpdate`, survive into the next frame. Add `App::set_run_mode` to change the run mode at runtime; `apply_loop_once` resets its state on any run-mode change, so loop modes can be entered and re-entered live. Reshape `RunMode` to just `UntilExit` and `LoopNTimes`, dropping the unused and undocumented `Ticks` / `Duration` / `once()` - `once()` (quit after one frame) was easily confused with `loop_once()` (hold after one frame), and "run N frames then quit" is a one-line `app.quit()` from `update`. Also fix two stale `LoopMode` doc references and document the `freeze()` caveat.
`loop_once` draws a static spiral once via `sketch(view).loop_once()` and holds it. `run_modes` switches run/update modes at runtime with the keyboard (Continuous, reactive rate, wait, loop_once, loop_ntimes(60)) - a moving dot and update counter make each mode's cadence tangible.
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.
Summary
Reintroduces the pre-0.20
LoopMode::loop_once()behaviour that was lost in the Bevy port, and cleans up the surroundingRunModeAPI.Previously,
set_update_mode(UpdateMode::reactive(..))keeps re-running on mouse movement, and no singleUpdateModecan both ignore input and stay responsive, becausebevy_winitcannot distinguish cursor movement from window close/resize (both are "window events").What this adds
RunMode::loop_once()andRunMode::loop_ntimes(n), with matching.loop_once()/.loop_ntimes(n)shortcuts on both the appBuilderand theSketchBuilder.updateandvieweach run exactlyntimes (default 1), then the last frame is held on screen while the window idles and stays closable and resizable. This is the intended mode for static, sketch-based compositions.App::set_run_mode(..)to change the run mode at runtime, mirroringApp::set_update_mode. Loop modes can be entered and re-entered live.examples/nannou_basics/:loop_once- draws a static spiral once and holds it, andrun_modes- switches between Continuous, reactive rate, wait, and the loop modes at runtime with the keyboard.How the frame is held
Simply capping
viewpresents a black window. Under Bevy the drawn frame lives in the camera's persistent intermediate render texture, which is only kept populated while the window keeps drawing. Once drawing stops, empty passes plus MSAA-writeback ping-pong plusTextureCacheeviction leave it black, so unfortunately our old approach of just re-using the texture is blocked at the moment.Instead, in this workaround we freeze the
Drawinstance itself. A newnannou_draw::DrawFrozenresource gates the per-frame draw systems (reset_draw,clear_previous_frame,update_draw_mesh), so the last frame's rendered meshes and clear color persist and the render graph keeps drawing them.update/vieware then skipped too, so they run exactlyntimes even for a sketch whoseviewreads live time or input. ALast-schedule system (apply_loop_once) counts rendered frames and, once the budget is spent, freezes the draw and switches tofreeze()to idle. It runs inLastso the final frame's meshes, spawned inPostUpdate, survive into the next frame.Breaking change:
RunModetrimmedRunModeis reduced toUntilExitandLoopNTimes. TheTicks,Durationandonce()variants (run N frames or a duration, then quit) are removed. They were unused and undocumented, andonce()(quit after one frame) was easily confused withloop_once()(hold after one frame). To run a fixed number of frames and then quit, callApp::quit()from your ownupdateonce a counter reaches the target.Migrating from the pre-0.20
LoopModeLoopMode::loop_once()->RunMode::loop_once()(or.loop_once()on the app or sketch builder).LoopMode::loop_ntimes(n)->RunMode::loop_ntimes(n).LoopMode::Wait->app.set_update_mode(UpdateMode::wait()).LoopMode::Rate/rate_fps(fps)->app.set_update_rate(fps)orUpdateMode::rate(hz).LoopMode::RefreshSync->UpdateMode::Continuous(the default).