Path: blob/main/crates/bevy_render/src/extract_plugin.rs
9311 views
use crate::{1sync_world::{despawn_temporary_render_entities, entity_sync_system, SyncWorldPlugin},2Render, RenderApp, RenderSystems,3};4use bevy_app::{App, Plugin, SubApp};5use bevy_derive::{Deref, DerefMut};6use bevy_ecs::{7resource::Resource,8schedule::{IntoScheduleConfigs, Schedule, ScheduleBuildSettings, ScheduleLabel, Schedules},9world::{Mut, World},10};11use bevy_utils::default;1213/// Plugin that sets up the [`RenderApp`] and handles extracting data from the14/// main world to the render world.15pub struct ExtractPlugin {16/// Function that gets run at the beginning of each extraction.17///18/// Gets the main world and render world as arguments (in that order).19pub pre_extract: fn(&mut World, &mut World),20}2122impl Default for ExtractPlugin {23fn default() -> Self {24Self {25pre_extract: |_, _| {},26}27}28}2930impl Plugin for ExtractPlugin {31fn build(&self, app: &mut App) {32app.add_plugins(SyncWorldPlugin);33app.init_resource::<ScratchMainWorld>();3435let mut render_app = SubApp::new();3637let mut extract_schedule = Schedule::new(ExtractSchedule);38// We skip applying any commands during the ExtractSchedule39// so commands can be applied on the render thread.40extract_schedule.set_build_settings(ScheduleBuildSettings {41auto_insert_apply_deferred: false,42..default()43});44extract_schedule.set_apply_final_deferred(false);4546render_app.add_schedule(Render::base_schedule());47render_app.add_schedule(extract_schedule);48render_app.add_systems(49Render,50(51// This set applies the commands from the extract schedule while the render schedule52// is running in parallel with the main app.53apply_extract_commands.in_set(RenderSystems::ExtractCommands),54despawn_temporary_render_entities.in_set(RenderSystems::PostCleanup),55),56);5758let pre_extract = self.pre_extract;59render_app.set_extract(move |main_world, render_world| {60pre_extract(main_world, render_world);6162{63#[cfg(feature = "trace")]64let _stage_span = bevy_log::info_span!("entity_sync").entered();65entity_sync_system(main_world, render_world);66}6768// run extract schedule69extract(main_world, render_world);70});7172let (sender, receiver) = bevy_time::create_time_channels();73render_app.insert_resource(sender);74app.insert_resource(receiver);75app.insert_sub_app(RenderApp, render_app);76}77}7879/// Schedule in which data from the main world is 'extracted' into the render world.80///81/// This step should be kept as short as possible to increase the "pipelining potential" for82/// running the next frame while rendering the current frame.83///84/// This schedule is run on the render world, but it also has access to the main world.85/// See [`MainWorld`] and [`Extract`](crate::Extract) for details on how to access main world data from this schedule.86#[derive(ScheduleLabel, PartialEq, Eq, Debug, Clone, Hash, Default)]87pub struct ExtractSchedule;8889/// Applies the commands from the extract schedule. This happens during90/// the render schedule rather than during extraction to allow the commands to run in parallel with the91/// main app when pipelined rendering is enabled.92fn apply_extract_commands(render_world: &mut World) {93render_world.resource_scope(|render_world, mut schedules: Mut<Schedules>| {94schedules95.get_mut(ExtractSchedule)96.unwrap()97.apply_deferred(render_world);98});99}100/// The simulation [`World`] of the application, stored as a resource.101///102/// This resource is only available during [`ExtractSchedule`] and not103/// during command application of that schedule.104/// See [`Extract`](crate::Extract) for more details.105#[derive(Resource, Default, Deref, DerefMut)]106pub struct MainWorld(World);107108/// A "scratch" world used to avoid allocating new worlds every frame when109/// swapping out the [`MainWorld`] for [`ExtractSchedule`].110#[derive(Resource, Default)]111struct ScratchMainWorld(World);112113/// Executes the [`ExtractSchedule`] step of the renderer.114/// This updates the render world with the extracted ECS data of the current frame.115pub fn extract(main_world: &mut World, render_world: &mut World) {116// temporarily add the app world to the render world as a resource117let scratch_world = main_world.remove_resource::<ScratchMainWorld>().unwrap();118let inserted_world = core::mem::replace(main_world, scratch_world.0);119render_world.insert_resource(MainWorld(inserted_world));120render_world.run_schedule(ExtractSchedule);121122// move the app world back, as if nothing happened.123let inserted_world = render_world.remove_resource::<MainWorld>().unwrap();124let scratch_world = core::mem::replace(main_world, inserted_world.0);125main_world.insert_resource(ScratchMainWorld(scratch_world));126}127128#[cfg(test)]129mod test {130use bevy_app::{App, Startup};131use bevy_ecs::{prelude::*, schedule::ScheduleLabel};132133use crate::{134extract_component::{ExtractComponent, ExtractComponentPlugin},135extract_plugin::ExtractPlugin,136sync_component::SyncComponent,137sync_world::MainEntity,138Render, RenderApp,139};140141#[derive(Component, Clone, Debug)]142struct RenderComponent;143144#[derive(Component, Clone, Debug)]145struct RenderComponentExtra;146147#[derive(Component, Clone, Debug, ExtractComponent)]148struct RenderComponentSeparate;149150#[derive(Component, Clone, Debug)]151struct RenderComponentNoExtract;152153impl SyncComponent for RenderComponent {154type Out = (RenderComponent, RenderComponentExtra);155}156157impl ExtractComponent for RenderComponent {158type QueryData = &'static Self;159160type QueryFilter = ();161162fn extract_component(163_item: bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>,164) -> Option<Self::Out> {165Some((RenderComponent, RenderComponentExtra))166}167}168169#[test]170fn extraction_works() {171let mut app = App::new();172173app.add_plugins(ExtractPlugin::default());174app.add_plugins(ExtractComponentPlugin::<RenderComponent>::default());175app.add_plugins(ExtractComponentPlugin::<RenderComponentSeparate>::default());176app.add_systems(Startup, |mut commands: Commands| {177commands.spawn((RenderComponent, RenderComponentSeparate));178});179180let render_app = app.get_sub_app_mut(RenderApp).unwrap();181182// Normally RenderPlugin sets the RenderRecovery schedule as update, but for183// testing we just use the Render schedule directly.184render_app.update_schedule = Some(Render.intern());185186render_app.world_mut().add_observer(187|event: On<Add, (RenderComponent, RenderComponentExtra)>, mut commands: Commands| {188// Simulate data that's not extracted189commands190.entity(event.entity)191.insert(RenderComponentNoExtract);192},193);194195app.update();196197// Check that all components have been extracted198{199let render_app = app.get_sub_app_mut(RenderApp).unwrap();200render_app201.world_mut()202.run_system_cached(203|entity: Single<(204&MainEntity,205Option<&RenderComponent>,206Option<&RenderComponentExtra>,207Option<&RenderComponentSeparate>,208Option<&RenderComponentNoExtract>,209)>| {210assert!(entity.1.is_some());211assert!(entity.2.is_some());212assert!(entity.3.is_some());213assert!(entity.4.is_some());214},215)216.unwrap();217}218219// Remove RenderComponent220app.world_mut()221.run_system_cached(222|mut commands: Commands, query: Query<Entity, With<RenderComponent>>| {223for entity in query {224commands.entity(entity).remove::<RenderComponent>();225}226},227)228.unwrap();229230app.update();231232// Check that the extracted components have been removed233{234let render_app = app.get_sub_app_mut(RenderApp).unwrap();235render_app236.world_mut()237.run_system_cached(238|entity: Single<(239&MainEntity,240Option<&RenderComponent>,241Option<&RenderComponentExtra>,242Option<&RenderComponentSeparate>,243Option<&RenderComponentNoExtract>,244)>| {245assert!(entity.1.is_none());246assert!(entity.2.is_none());247assert!(entity.3.is_some());248assert!(entity.4.is_some());249},250)251.unwrap();252}253}254}255256257