Skip to content

Commit 7bcddbb

Browse files
hymmalradish
authored andcommitted
Separate Extract from Sub App Schedule (bevyengine#7046)
# Objective - This pulls out some of the changes to Plugin setup and sub apps from bevyengine#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 bevyengine#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 d761ab0 commit 7bcddbb

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
@@ -94,6 +94,10 @@ pub enum RenderStage {
9494
Cleanup,
9595
}
9696

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

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

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

374321
pub struct FrameCountPlugin;

0 commit comments

Comments
 (0)