Skip to content

Commit d76b53b

Browse files
committed
Separate Extract from Sub App Schedule (#7046)
# Objective - This pulls out some of the changes to Plugin setup and sub apps from #6503 to make that PR easier to review. - Separate the extract stage from running the sub app's schedule to allow for them to be run on separate threads in the future - Fixes #6990 ## Solution - add a run method to `SubApp` that runs the schedule - change the name of `sub_app_runner` to extract to make it clear that this function is only for extracting data between the main app and the sub app - remove the extract stage from the sub app schedule so it can be run separately. This is done by adding a `setup` method to the `Plugin` trait that runs after all plugin build methods run. This is required to allow the extract stage to be removed from the schedule after all the plugins have added their systems to the stage. We will also need the setup method for pipelined rendering to setup the render thread. See https://github.com/bevyengine/bevy/blob/e3267965e15f14be18eec942dcaf16807144eb05/crates/bevy_render/src/pipelined_rendering.rs#L57-L98 ## Changelog - Separate SubApp Extract stage from running the sub app schedule. ## Migration Guide ### SubApp `runner` has conceptually been changed to an `extract` function. The `runner` no longer is in charge of running the sub app schedule. It's only concern is now moving data between the main world and the sub app. The `sub_app.app.schedule` is now run for you after the provided function is called. ```rust // before fn main() { let sub_app = App::empty(); sub_app.add_stage(MyStage, SystemStage::parallel()); App::new().add_sub_app(MySubApp, sub_app, move |main_world, sub_app| { extract(app_world, render_app); render_app.app.schedule.run(); }); } // after fn main() { let sub_app = App::empty(); sub_app.add_stage(MyStage, SystemStage::parallel()); App::new().add_sub_app(MySubApp, sub_app, move |main_world, sub_app| { extract(app_world, render_app); // schedule is automatically called for you after extract is run }); } ```
1 parent e94215c commit d76b53b

File tree

4 files changed

+87
-99
lines changed

4 files changed

+87
-99
lines changed

crates/bevy_app/src/app.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,20 @@ impl Debug for App {
9090
/// Each `SubApp` has its own [`Schedule`] and [`World`], enabling a separation of concerns.
9191
struct SubApp {
9292
app: App,
93-
runner: Box<dyn Fn(&mut World, &mut App)>,
93+
extract: Box<dyn Fn(&mut World, &mut App)>,
94+
}
95+
96+
impl SubApp {
97+
/// Runs the `SubApp`'s schedule.
98+
pub fn run(&mut self) {
99+
self.app.schedule.run(&mut self.app.world);
100+
self.app.world.clear_trackers();
101+
}
102+
103+
/// Extracts data from main world to this sub-app.
104+
pub fn extract(&mut self, main_world: &mut World) {
105+
(self.extract)(main_world, &mut self.app);
106+
}
94107
}
95108

96109
impl Debug for SubApp {
@@ -153,8 +166,8 @@ impl App {
153166
self.schedule.run(&mut self.world);
154167

155168
for sub_app in self.sub_apps.values_mut() {
156-
(sub_app.runner)(&mut self.world, &mut sub_app.app);
157-
sub_app.app.world.clear_trackers();
169+
sub_app.extract(&mut self.world);
170+
sub_app.run();
158171
}
159172

160173
self.world.clear_trackers();
@@ -176,6 +189,14 @@ impl App {
176189
if app.is_building_plugin {
177190
panic!("App::run() was called from within Plugin::Build(), which is not allowed.");
178191
}
192+
193+
// temporarily remove the plugin registry to run each plugin's setup function on app.
194+
let mut plugin_registry = std::mem::take(&mut app.plugin_registry);
195+
for plugin in &plugin_registry {
196+
plugin.setup(&mut app);
197+
}
198+
std::mem::swap(&mut app.plugin_registry, &mut plugin_registry);
199+
179200
let runner = std::mem::replace(&mut app.runner, Box::new(run_once));
180201
(runner)(app);
181202
}
@@ -1004,13 +1025,13 @@ impl App {
10041025
&mut self,
10051026
label: impl AppLabel,
10061027
app: App,
1007-
sub_app_runner: impl Fn(&mut World, &mut App) + 'static,
1028+
extract: impl Fn(&mut World, &mut App) + 'static,
10081029
) -> &mut Self {
10091030
self.sub_apps.insert(
10101031
label.as_label(),
10111032
SubApp {
10121033
app,
1013-
runner: Box::new(sub_app_runner),
1034+
extract: Box::new(extract),
10141035
},
10151036
);
10161037
self

crates/bevy_app/src/plugin.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,19 @@ use std::any::Any;
1616
pub trait Plugin: Downcast + Any + Send + Sync {
1717
/// Configures the [`App`] to which this plugin is added.
1818
fn build(&self, app: &mut App);
19+
20+
/// Runs after all plugins are built, but before the app runner is called.
21+
/// This can be useful if you have some resource that other plugins need during their build step,
22+
/// but after build you want to remove it and send it to another thread.
23+
fn setup(&self, _app: &mut App) {
24+
// do nothing
25+
}
26+
1927
/// Configures a name for the [`Plugin`] which is primarily used for debugging.
2028
fn name(&self) -> &str {
2129
std::any::type_name::<Self>()
2230
}
31+
2332
/// If the plugin can be meaningfully instantiated several times in an [`App`](crate::App),
2433
/// override this method to return `false`.
2534
fn is_unique(&self) -> bool {

crates/bevy_ecs/src/schedule/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,17 @@ impl Schedule {
361361
.and_then(|stage| stage.downcast_mut::<T>())
362362
}
363363

364+
/// Removes a [`Stage`] from the schedule.
365+
pub fn remove_stage(&mut self, stage_label: impl StageLabel) -> Option<Box<dyn Stage>> {
366+
let label = stage_label.as_label();
367+
368+
let Some(index) = self.stage_order.iter().position(|x| *x == label) else {
369+
return None;
370+
};
371+
self.stage_order.remove(index);
372+
self.stages.remove(&label)
373+
}
374+
364375
/// Executes each [`Stage`] contained in the schedule, one at a time.
365376
pub fn run_once(&mut self, world: &mut World) {
366377
for label in &self.stage_order {

crates/bevy_render/src/lib.rs

Lines changed: 41 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ pub enum RenderStage {
9292
Cleanup,
9393
}
9494

95+
/// Resource for holding the extract stage of the rendering schedule.
96+
#[derive(Resource)]
97+
pub struct ExtractStage(pub SystemStage);
98+
9599
/// The simulation [`World`] of the application, stored as a resource.
96100
/// This resource is only available during [`RenderStage::Extract`] and not
97101
/// during command application of that stage.
@@ -198,7 +202,10 @@ impl Plugin for RenderPlugin {
198202
.with_system(PipelineCache::process_pipeline_queue_system)
199203
.with_system(render_system.at_end()),
200204
)
201-
.add_stage(RenderStage::Cleanup, SystemStage::parallel())
205+
.add_stage(
206+
RenderStage::Cleanup,
207+
SystemStage::parallel().with_system(World::clear_entities.at_end()),
208+
)
202209
.init_resource::<render_graph::RenderGraph>()
203210
.insert_resource(RenderInstance(instance))
204211
.insert_resource(device)
@@ -248,78 +255,6 @@ impl Plugin for RenderPlugin {
248255
// extract
249256
extract(app_world, render_app);
250257
}
251-
252-
{
253-
#[cfg(feature = "trace")]
254-
let _stage_span =
255-
bevy_utils::tracing::info_span!("stage", name = "prepare").entered();
256-
257-
// prepare
258-
let prepare = render_app
259-
.schedule
260-
.get_stage_mut::<SystemStage>(RenderStage::Prepare)
261-
.unwrap();
262-
prepare.run(&mut render_app.world);
263-
}
264-
265-
{
266-
#[cfg(feature = "trace")]
267-
let _stage_span =
268-
bevy_utils::tracing::info_span!("stage", name = "queue").entered();
269-
270-
// queue
271-
let queue = render_app
272-
.schedule
273-
.get_stage_mut::<SystemStage>(RenderStage::Queue)
274-
.unwrap();
275-
queue.run(&mut render_app.world);
276-
}
277-
278-
{
279-
#[cfg(feature = "trace")]
280-
let _stage_span =
281-
bevy_utils::tracing::info_span!("stage", name = "sort").entered();
282-
283-
// phase sort
284-
let phase_sort = render_app
285-
.schedule
286-
.get_stage_mut::<SystemStage>(RenderStage::PhaseSort)
287-
.unwrap();
288-
phase_sort.run(&mut render_app.world);
289-
}
290-
291-
{
292-
#[cfg(feature = "trace")]
293-
let _stage_span =
294-
bevy_utils::tracing::info_span!("stage", name = "render").entered();
295-
296-
// render
297-
let render = render_app
298-
.schedule
299-
.get_stage_mut::<SystemStage>(RenderStage::Render)
300-
.unwrap();
301-
render.run(&mut render_app.world);
302-
}
303-
304-
{
305-
#[cfg(feature = "trace")]
306-
let _stage_span =
307-
bevy_utils::tracing::info_span!("stage", name = "cleanup").entered();
308-
309-
// cleanup
310-
let cleanup = render_app
311-
.schedule
312-
.get_stage_mut::<SystemStage>(RenderStage::Cleanup)
313-
.unwrap();
314-
cleanup.run(&mut render_app.world);
315-
}
316-
{
317-
#[cfg(feature = "trace")]
318-
let _stage_span =
319-
bevy_utils::tracing::info_span!("stage", name = "clear_entities").entered();
320-
321-
render_app.world.clear_entities();
322-
}
323258
});
324259
}
325260

@@ -335,6 +270,20 @@ impl Plugin for RenderPlugin {
335270
.register_type::<primitives::CubemapFrusta>()
336271
.register_type::<primitives::Frustum>();
337272
}
273+
274+
fn setup(&self, app: &mut App) {
275+
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
276+
// move the extract stage to a resource so render_app.run() does not run it.
277+
let stage = render_app
278+
.schedule
279+
.remove_stage(RenderStage::Extract)
280+
.unwrap()
281+
.downcast::<SystemStage>()
282+
.unwrap();
283+
284+
render_app.world.insert_resource(ExtractStage(*stage));
285+
}
286+
}
338287
}
339288

340289
/// A "scratch" world used to avoid allocating new worlds every frame when
@@ -345,25 +294,23 @@ struct ScratchMainWorld(World);
345294
/// Executes the [`Extract`](RenderStage::Extract) stage of the renderer.
346295
/// This updates the render world with the extracted ECS data of the current frame.
347296
fn extract(app_world: &mut World, render_app: &mut App) {
348-
let extract = render_app
349-
.schedule
350-
.get_stage_mut::<SystemStage>(RenderStage::Extract)
351-
.unwrap();
352-
353-
// temporarily add the app world to the render world as a resource
354-
let scratch_world = app_world.remove_resource::<ScratchMainWorld>().unwrap();
355-
let inserted_world = std::mem::replace(app_world, scratch_world.0);
356-
let running_world = &mut render_app.world;
357-
running_world.insert_resource(MainWorld(inserted_world));
358-
359-
extract.run(running_world);
360-
// move the app world back, as if nothing happened.
361-
let inserted_world = running_world.remove_resource::<MainWorld>().unwrap();
362-
let scratch_world = std::mem::replace(app_world, inserted_world.0);
363-
app_world.insert_resource(ScratchMainWorld(scratch_world));
364-
365-
// Note: We apply buffers (read, Commands) after the `MainWorld` has been removed from the render app's world
366-
// so that in future, pipelining will be able to do this too without any code relying on it.
367-
// see <https://github.com/bevyengine/bevy/issues/5082>
368-
extract.apply_buffers(running_world);
297+
render_app
298+
.world
299+
.resource_scope(|render_world, mut extract_stage: Mut<ExtractStage>| {
300+
// temporarily add the app world to the render world as a resource
301+
let scratch_world = app_world.remove_resource::<ScratchMainWorld>().unwrap();
302+
let inserted_world = std::mem::replace(app_world, scratch_world.0);
303+
render_world.insert_resource(MainWorld(inserted_world));
304+
305+
extract_stage.0.run(render_world);
306+
// move the app world back, as if nothing happened.
307+
let inserted_world = render_world.remove_resource::<MainWorld>().unwrap();
308+
let scratch_world = std::mem::replace(app_world, inserted_world.0);
309+
app_world.insert_resource(ScratchMainWorld(scratch_world));
310+
311+
// Note: We apply buffers (read, Commands) after the `MainWorld` has been removed from the render app's world
312+
// so that in future, pipelining will be able to do this too without any code relying on it.
313+
// see <https://github.com/bevyengine/bevy/issues/5082>
314+
extract_stage.0.apply_buffers(render_world);
315+
});
369316
}

0 commit comments

Comments
 (0)