Skip to content

Commit 2c6cf9a

Browse files
authored
Run RenderStartup in/before extract instead of after it. (#19926)
# Objective - Fixes #19910. ## Solution - First, allow extraction function to be FnMut instead of Fn. FnMut is a superset of Fn anyway, and we only ever call this function once at a time (we would never call this in parallel for different pairs of worlds or something). - Run the `RenderStartup` schedule in the extract function with a flag to only do it once. - Remove all the `MainRender` stuff. One sad part here is that now the `RenderStartup` blocks extraction. So for pipelined rendering, our simulation will be blocked on the first frame while we set up all the rendering resources. I don't see this as a big loss though since A) that is fundamentally what we want here - extraction **has to** run after `RenderStartup`, and the only way to do better is to somehow run `RenderStartup` in parallel with the first simulation frame, and B) without `RenderStartup` the **entire** app was blocked on initializing render resources during Plugin construction - so we're not really losing anything here. ## Testing - I ran the `custom_post_processing` example (which was ported to use `RenderStartup` in #19886) and it still works.
1 parent b625043 commit 2c6cf9a

File tree

3 files changed

+28
-23
lines changed

3 files changed

+28
-23
lines changed

crates/bevy_app/src/sub_app.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use core::fmt::Debug;
1212
#[cfg(feature = "trace")]
1313
use tracing::info_span;
1414

15-
type ExtractFn = Box<dyn Fn(&mut World, &mut World) + Send>;
15+
type ExtractFn = Box<dyn FnMut(&mut World, &mut World) + Send>;
1616

1717
/// A secondary application with its own [`World`]. These can run independently of each other.
1818
///
@@ -160,7 +160,7 @@ impl SubApp {
160160
/// The first argument is the `World` to extract data from, the second argument is the app `World`.
161161
pub fn set_extract<F>(&mut self, extract: F) -> &mut Self
162162
where
163-
F: Fn(&mut World, &mut World) + Send + 'static,
163+
F: FnMut(&mut World, &mut World) + Send + 'static,
164164
{
165165
self.extract = Some(Box::new(extract));
166166
self
@@ -177,13 +177,13 @@ impl SubApp {
177177
/// ```
178178
/// # use bevy_app::SubApp;
179179
/// # let mut app = SubApp::new();
180-
/// let default_fn = app.take_extract();
180+
/// let mut default_fn = app.take_extract();
181181
/// app.set_extract(move |main, render| {
182182
/// // Do pre-extract custom logic
183183
/// // [...]
184184
///
185185
/// // Call Bevy's default, which executes the Extract phase
186-
/// if let Some(f) = default_fn.as_ref() {
186+
/// if let Some(f) = default_fn.as_mut() {
187187
/// f(main, render);
188188
/// }
189189
///

crates/bevy_render/src/lib.rs

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -221,22 +221,6 @@ pub enum RenderSystems {
221221
PostCleanup,
222222
}
223223

224-
/// The schedule that contains the app logic that is evaluated each tick
225-
///
226-
/// This is highly inspired by [`bevy_app::Main`]
227-
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
228-
pub struct MainRender;
229-
impl MainRender {
230-
pub fn run(world: &mut World, mut run_at_least_once: Local<bool>) {
231-
if !*run_at_least_once {
232-
let _ = world.try_run_schedule(RenderStartup);
233-
*run_at_least_once = true;
234-
}
235-
236-
let _ = world.try_run_schedule(Render);
237-
}
238-
}
239-
240224
/// Deprecated alias for [`RenderSystems`].
241225
#[deprecated(since = "0.17.0", note = "Renamed to `RenderSystems`.")]
242226
pub type RenderSet = RenderSystems;
@@ -561,7 +545,7 @@ unsafe fn initialize_render_app(app: &mut App) {
561545
app.init_resource::<ScratchMainWorld>();
562546

563547
let mut render_app = SubApp::new();
564-
render_app.update_schedule = Some(MainRender.intern());
548+
render_app.update_schedule = Some(Render.intern());
565549

566550
let mut extract_schedule = Schedule::new(ExtractSchedule);
567551
// We skip applying any commands during the ExtractSchedule
@@ -576,7 +560,6 @@ unsafe fn initialize_render_app(app: &mut App) {
576560
.add_schedule(extract_schedule)
577561
.add_schedule(Render::base_schedule())
578562
.init_resource::<render_graph::RenderGraph>()
579-
.add_systems(MainRender, MainRender::run)
580563
.insert_resource(app.world().resource::<AssetServer>().clone())
581564
.add_systems(ExtractSchedule, PipelineCache::extract_shaders)
582565
.add_systems(
@@ -592,7 +575,19 @@ unsafe fn initialize_render_app(app: &mut App) {
592575
),
593576
);
594577

595-
render_app.set_extract(|main_world, render_world| {
578+
// We want the closure to have a flag to only run the RenderStartup schedule once, but the only
579+
// way to have the closure store this flag is by capturing it. This variable is otherwise
580+
// unused.
581+
let mut should_run_startup = true;
582+
render_app.set_extract(move |main_world, render_world| {
583+
if should_run_startup {
584+
// Run the `RenderStartup` if it hasn't run yet. This does mean `RenderStartup` blocks
585+
// the rest of the app extraction, but this is necessary since extraction itself can
586+
// depend on resources initialized in `RenderStartup`.
587+
render_world.run_schedule(RenderStartup);
588+
should_run_startup = false;
589+
}
590+
596591
{
597592
#[cfg(feature = "trace")]
598593
let _stage_span = tracing::info_span!("entity_sync").entered();
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
title: `take_extract` now returns `dyn FnMut` instead of `dyn Fn`.
3+
pull_requests: [19926]
4+
---
5+
6+
Previously, `set_extract` accepted any `Fn`. Now we accept any `FnMut`. For callers of
7+
`set_extract`, there is no difference since `Fn: FnMut`.
8+
9+
However, callers of `take_extract` will now be returned
10+
`Option<Box<dyn FnMut(&mut World, &mut World) + Send>>` instead.

0 commit comments

Comments
 (0)