Skip to content
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 by Bors] - Separate Extract from Sub App Schedule #7046

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,20 @@ impl Debug for App {
/// Each `SubApp` has its own [`Schedule`] and [`World`], enabling a separation of concerns.
struct SubApp {
app: App,
runner: Box<dyn Fn(&mut World, &mut App)>,
extract: Box<dyn Fn(&mut World, &mut App)>,
}

impl SubApp {
/// Runs the `SubApp`'s schedule.
pub fn run(&mut self) {
self.app.schedule.run(&mut self.app.world);
self.app.world.clear_trackers();
}

/// Extracts data from main world to this sub-app.
pub fn extract(&mut self, main_world: &mut World) {
(self.extract)(main_world, &mut self.app);
}
}

impl Debug for SubApp {
Expand Down Expand Up @@ -153,8 +166,8 @@ impl App {
self.schedule.run(&mut self.world);

for sub_app in self.sub_apps.values_mut() {
(sub_app.runner)(&mut self.world, &mut sub_app.app);
sub_app.app.world.clear_trackers();
sub_app.extract(&mut self.world);
sub_app.run();
}

self.world.clear_trackers();
Expand All @@ -176,6 +189,14 @@ impl App {
if app.is_building_plugin {
panic!("App::run() was called from within Plugin::Build(), which is not allowed.");
}

// temporarily remove the plugin registry to run each plugin's setup function on app.
let mut plugin_registry = std::mem::take(&mut app.plugin_registry);
for plugin in &plugin_registry {
plugin.setup(&mut app);
}
std::mem::swap(&mut app.plugin_registry, &mut plugin_registry);

let runner = std::mem::replace(&mut app.runner, Box::new(run_once));
(runner)(app);
}
Expand Down Expand Up @@ -1004,13 +1025,13 @@ impl App {
&mut self,
label: impl AppLabel,
app: App,
sub_app_runner: impl Fn(&mut World, &mut App) + 'static,
extract: impl Fn(&mut World, &mut App) + 'static,
) -> &mut Self {
self.sub_apps.insert(
label.as_label(),
SubApp {
app,
runner: Box::new(sub_app_runner),
extract: Box::new(extract),
},
);
self
Expand Down
9 changes: 9 additions & 0 deletions crates/bevy_app/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,19 @@ use std::any::Any;
pub trait Plugin: Downcast + Any + Send + Sync {
/// Configures the [`App`] to which this plugin is added.
fn build(&self, app: &mut App);

/// Runs after all plugins are built, but before the app runner is called.
/// This can be useful if you have some resource that other plugins need during their build step,
/// but after build you want to remove it and send it to another thread.
fn setup(&self, _app: &mut App) {
// do nothing
}

/// Configures a name for the [`Plugin`] which is primarily used for debugging.
fn name(&self) -> &str {
std::any::type_name::<Self>()
}

/// If the plugin can be meaningfully instantiated several times in an [`App`](crate::App),
/// override this method to return `false`.
fn is_unique(&self) -> bool {
Expand Down
11 changes: 11 additions & 0 deletions crates/bevy_ecs/src/schedule/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,17 @@ impl Schedule {
.and_then(|stage| stage.downcast_mut::<T>())
}

/// Removes a [`Stage`] from the schedule.
pub fn remove_stage(&mut self, stage_label: impl StageLabel) -> Option<Box<dyn Stage>> {
let label = stage_label.as_label();

let Some(index) = self.stage_order.iter().position(|x| *x == label) else {
return None;
};
self.stage_order.remove(index);
self.stages.remove(&label)
}

/// Executes each [`Stage`] contained in the schedule, one at a time.
pub fn run_once(&mut self, world: &mut World) {
for label in &self.stage_order {
Expand Down
135 changes: 41 additions & 94 deletions crates/bevy_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ pub enum RenderStage {
Cleanup,
}

/// Resource for holding the extract stage of the rendering schedule.
#[derive(Resource)]
pub struct ExtractStage(pub SystemStage);

/// The simulation [`World`] of the application, stored as a resource.
/// This resource is only available during [`RenderStage::Extract`] and not
/// during command application of that stage.
Expand Down Expand Up @@ -198,7 +202,10 @@ impl Plugin for RenderPlugin {
.with_system(PipelineCache::process_pipeline_queue_system)
.with_system(render_system.at_end()),
)
.add_stage(RenderStage::Cleanup, SystemStage::parallel())
.add_stage(
RenderStage::Cleanup,
SystemStage::parallel().with_system(World::clear_entities.at_end()),
)
.init_resource::<render_graph::RenderGraph>()
.insert_resource(RenderInstance(instance))
.insert_resource(device)
Expand Down Expand Up @@ -248,78 +255,6 @@ impl Plugin for RenderPlugin {
// extract
extract(app_world, render_app);
}

{
#[cfg(feature = "trace")]
let _stage_span =
bevy_utils::tracing::info_span!("stage", name = "prepare").entered();

// prepare
let prepare = render_app
.schedule
.get_stage_mut::<SystemStage>(RenderStage::Prepare)
.unwrap();
prepare.run(&mut render_app.world);
}

{
#[cfg(feature = "trace")]
let _stage_span =
bevy_utils::tracing::info_span!("stage", name = "queue").entered();

// queue
let queue = render_app
.schedule
.get_stage_mut::<SystemStage>(RenderStage::Queue)
.unwrap();
queue.run(&mut render_app.world);
}

{
#[cfg(feature = "trace")]
let _stage_span =
bevy_utils::tracing::info_span!("stage", name = "sort").entered();

// phase sort
let phase_sort = render_app
.schedule
.get_stage_mut::<SystemStage>(RenderStage::PhaseSort)
.unwrap();
phase_sort.run(&mut render_app.world);
}

{
#[cfg(feature = "trace")]
let _stage_span =
bevy_utils::tracing::info_span!("stage", name = "render").entered();

// render
let render = render_app
.schedule
.get_stage_mut::<SystemStage>(RenderStage::Render)
.unwrap();
render.run(&mut render_app.world);
}

{
#[cfg(feature = "trace")]
let _stage_span =
bevy_utils::tracing::info_span!("stage", name = "cleanup").entered();

// cleanup
let cleanup = render_app
.schedule
.get_stage_mut::<SystemStage>(RenderStage::Cleanup)
.unwrap();
cleanup.run(&mut render_app.world);
}
{
#[cfg(feature = "trace")]
let _stage_span =
bevy_utils::tracing::info_span!("stage", name = "clear_entities").entered();

render_app.world.clear_entities();
}
});
}

Expand All @@ -335,6 +270,20 @@ impl Plugin for RenderPlugin {
.register_type::<primitives::CubemapFrusta>()
.register_type::<primitives::Frustum>();
}

fn setup(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
// move the extract stage to a resource so render_app.run() does not run it.
let stage = render_app
.schedule
.remove_stage(RenderStage::Extract)
.unwrap()
.downcast::<SystemStage>()
.unwrap();

render_app.world.insert_resource(ExtractStage(*stage));
}
}
}

/// A "scratch" world used to avoid allocating new worlds every frame when
Expand All @@ -345,25 +294,23 @@ struct ScratchMainWorld(World);
/// Executes the [`Extract`](RenderStage::Extract) stage of the renderer.
/// This updates the render world with the extracted ECS data of the current frame.
fn extract(app_world: &mut World, render_app: &mut App) {
let extract = render_app
.schedule
.get_stage_mut::<SystemStage>(RenderStage::Extract)
.unwrap();

// temporarily add the app world to the render world as a resource
let scratch_world = app_world.remove_resource::<ScratchMainWorld>().unwrap();
let inserted_world = std::mem::replace(app_world, scratch_world.0);
let running_world = &mut render_app.world;
running_world.insert_resource(MainWorld(inserted_world));

extract.run(running_world);
// move the app world back, as if nothing happened.
let inserted_world = running_world.remove_resource::<MainWorld>().unwrap();
let scratch_world = std::mem::replace(app_world, inserted_world.0);
app_world.insert_resource(ScratchMainWorld(scratch_world));

// Note: We apply buffers (read, Commands) after the `MainWorld` has been removed from the render app's world
// so that in future, pipelining will be able to do this too without any code relying on it.
// see <https://github.com/bevyengine/bevy/issues/5082>
extract.apply_buffers(running_world);
render_app
.world
.resource_scope(|render_world, mut extract_stage: Mut<ExtractStage>| {
// temporarily add the app world to the render world as a resource
let scratch_world = app_world.remove_resource::<ScratchMainWorld>().unwrap();
let inserted_world = std::mem::replace(app_world, scratch_world.0);
render_world.insert_resource(MainWorld(inserted_world));

extract_stage.0.run(render_world);
// move the app world back, as if nothing happened.
let inserted_world = render_world.remove_resource::<MainWorld>().unwrap();
let scratch_world = std::mem::replace(app_world, inserted_world.0);
app_world.insert_resource(ScratchMainWorld(scratch_world));

// Note: We apply buffers (read, Commands) after the `MainWorld` has been removed from the render app's world
// so that in future, pipelining will be able to do this too without any code relying on it.
// see <https://github.com/bevyengine/bevy/issues/5082>
extract_stage.0.apply_buffers(render_world);
});
}