Skip to main content

bevy_extract/
extract_plugin.rs

1use core::marker::PhantomData;
2
3use crate::sync_world::{despawn_temporary_entities, entity_sync_system, SyncWorldPlugin};
4use bevy_app::{App, AppLabel, Plugin, SubApp};
5use bevy_derive::{Deref, DerefMut};
6use bevy_ecs::{
7    resource::Resource,
8    schedule::{
9        InternedScheduleLabel, InternedSystemSet, IntoScheduleConfigs, Schedule,
10        ScheduleBuildSettings, ScheduleLabel, Schedules,
11    },
12    world::{Mut, World},
13};
14use bevy_utils::default;
15
16/// Plugin that sets up the sub app for the [`AppLabel`] and handles extracting data from the
17/// main world to the sub world.
18pub struct ExtractPlugin<L: AppLabel + Default> {
19    /// Function that gets run at the beginning of each extraction.
20    ///
21    /// Gets the main world and sub world as arguments (in that order).
22    pub pre_extract: fn(&mut World, &mut World),
23
24    marker: PhantomData<L>,
25
26    pub base_schedule: fn() -> Schedule,
27    pub schedule_label: InternedScheduleLabel,
28
29    pub extract_set: InternedSystemSet,
30    pub despawn_set: InternedSystemSet,
31}
32
33impl<L: AppLabel + Default> ExtractPlugin<L> {
34    pub fn new(
35        pre_extract: fn(&mut World, &mut World),
36        base_schedule: fn() -> Schedule,
37        schedule_label: InternedScheduleLabel,
38        extract_set: InternedSystemSet,
39        despawn_set: InternedSystemSet,
40    ) -> Self {
41        Self {
42            pre_extract,
43            marker: PhantomData,
44            base_schedule,
45            schedule_label,
46            extract_set,
47            despawn_set,
48        }
49    }
50}
51
52impl<L: AppLabel + Default + Copy + Eq> Plugin for ExtractPlugin<L> {
53    fn build(&self, app: &mut App) {
54        app.add_plugins(SyncWorldPlugin::<L>::default());
55        app.init_resource::<ScratchMainWorld>();
56
57        let mut sub_app = SubApp::new();
58
59        let mut extract_schedule = Schedule::new(ExtractSchedule);
60        // We skip applying any commands during the ExtractSchedule
61        // so commands can be applied on the sub app’s thread.
62        extract_schedule.set_build_settings(ScheduleBuildSettings {
63            auto_insert_apply_deferred: false,
64            ..default()
65        });
66        extract_schedule.set_apply_final_deferred(false);
67
68        sub_app
69            .add_schedule((self.base_schedule)())
70            .add_schedule(extract_schedule)
71            .allow_ambiguous_resource::<MainWorld>()
72            .add_systems(
73                self.schedule_label,
74                (
75                    // This set applies the commands from the extract schedule while the sub schedule
76                    // is running in parallel with the main app.
77                    apply_extract_commands.in_set(self.extract_set),
78                    despawn_temporary_entities::<L>.in_set(self.despawn_set),
79                ),
80            );
81
82        let pre_extract = self.pre_extract;
83        sub_app.set_extract(move |main_world, sub_world| {
84            pre_extract(main_world, sub_world);
85
86            {
87                #[cfg(feature = "trace")]
88                let _stage_span = bevy_log::info_span!("entity_sync").entered();
89                entity_sync_system::<L>(main_world, sub_world);
90            }
91
92            // run extract schedule
93            extract(main_world, sub_world);
94        });
95
96        app.insert_sub_app(L::default(), sub_app);
97    }
98}
99
100/// Schedule in which data from the main world is 'extracted' into the sub world.
101///
102/// This step should be kept as short as possible to increase the "pipelining potential" for
103/// running the next frame while processing the current frame.
104///
105/// This schedule is run on the sub world, but it also has access to the main world.
106/// See [`MainWorld`] and [`Extract`](crate::Extract) for details on how to access main world data from this schedule.
107#[derive(ScheduleLabel, PartialEq, Eq, Debug, Clone, Hash, Default)]
108pub struct ExtractSchedule;
109
110/// Applies the commands from the extract schedule. This happens during
111/// the sub schedule rather than during extraction to allow the commands to run in parallel with the
112/// main app when pipelined processing is enabled.
113fn apply_extract_commands(sub_world: &mut World) {
114    sub_world.resource_scope(|sub_world, mut schedules: Mut<Schedules>| {
115        schedules
116            .get_mut(ExtractSchedule)
117            .unwrap()
118            .apply_deferred(sub_world);
119    });
120}
121/// The simulation [`World`] of the application, stored as a resource.
122///
123/// This resource is only available during [`ExtractSchedule`] and not
124/// during command application of that schedule.
125/// See [`Extract`](crate::Extract) for more details.
126#[derive(Resource, Default, Deref, DerefMut)]
127pub struct MainWorld(World);
128
129/// A "scratch" world used to avoid allocating new worlds every frame when
130/// swapping out the [`MainWorld`] for [`ExtractSchedule`].
131#[derive(Resource, Default)]
132struct ScratchMainWorld(World);
133
134/// Executes the [`ExtractSchedule`] step of the processor.
135/// This updates the sub world with the extracted ECS data of the current frame.
136pub fn extract(main_world: &mut World, sub_world: &mut World) {
137    // temporarily add the app world to the sub world as a resource
138    let scratch_world = main_world.remove_resource::<ScratchMainWorld>().unwrap();
139    let inserted_world = core::mem::replace(main_world, scratch_world.0);
140    sub_world.insert_resource(MainWorld(inserted_world));
141    sub_world.run_schedule(ExtractSchedule);
142
143    // move the app world back, as if nothing happened.
144    let inserted_world = sub_world.remove_resource::<MainWorld>().unwrap();
145    let scratch_world = core::mem::replace(main_world, inserted_world.0);
146    main_world.insert_resource(ScratchMainWorld(scratch_world));
147}
148
149#[cfg(test)]
150mod test {
151    use bevy_app::{App, AppLabel, Startup};
152    use bevy_ecs::{prelude::*, schedule::ScheduleLabel};
153
154    use crate::{
155        extract_component::{ExtractComponent, ExtractComponentPlugin},
156        extract_plugin::ExtractPlugin,
157        sync_component::SyncComponent,
158        sync_world::MainEntity,
159    };
160
161    #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
162    pub enum MyScheduleSystems {
163        ExtractCommands,
164        PostCleanup,
165    }
166
167    #[derive(AppLabel, Debug, Hash, PartialEq, Eq, Clone, Default, Copy)]
168    pub struct ExtractApp;
169
170    #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
171    pub struct MySchedule;
172
173    impl MySchedule {
174        /// Sets up the base structure of the processing [`Schedule`].
175        ///
176        /// The sets defined in this enum are configured to run in order.
177        pub fn base_schedule() -> Schedule {
178            use MyScheduleSystems::*;
179
180            let mut schedule = Schedule::new(Self);
181
182            schedule.configure_sets((ExtractCommands, PostCleanup).chain());
183
184            schedule
185        }
186    }
187
188    #[derive(Component, Clone, Debug)]
189    struct RenderComponent;
190
191    #[derive(Component, Clone, Debug)]
192    struct RenderComponentExtra;
193
194    #[derive(Component, Clone, Debug, ExtractComponent)]
195    #[extract_app(ExtractApp)]
196    struct RenderComponentSeparate;
197
198    #[derive(Component, Clone, Debug)]
199    struct RenderComponentNoExtract;
200
201    impl SyncComponent<ExtractApp> for RenderComponent {
202        type Target = (RenderComponent, RenderComponentExtra);
203    }
204
205    impl ExtractComponent<ExtractApp> for RenderComponent {
206        type QueryData = &'static Self;
207        type QueryFilter = ();
208        type Out = (RenderComponent, RenderComponentExtra);
209
210        fn extract_component(
211            _item: bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>,
212        ) -> Option<Self::Out> {
213            Some((RenderComponent, RenderComponentExtra))
214        }
215    }
216
217    #[test]
218    fn extraction_works() {
219        let mut app = App::new();
220
221        app.add_plugins(ExtractPlugin::<ExtractApp>::new(
222            |_, _| {},
223            MySchedule::base_schedule,
224            MySchedule.intern(),
225            MyScheduleSystems::ExtractCommands.intern(),
226            MyScheduleSystems::PostCleanup.intern(),
227        ));
228        app.add_plugins(ExtractComponentPlugin::<RenderComponent, ExtractApp>::default());
229        app.add_plugins(ExtractComponentPlugin::<RenderComponentSeparate, ExtractApp>::default());
230        app.add_systems(Startup, |mut commands: Commands| {
231            commands.spawn((RenderComponent, RenderComponentSeparate));
232        });
233
234        let sub_app = app.get_sub_app_mut(ExtractApp).unwrap();
235
236        // Normally RenderPlugin sets the RenderRecovery schedule as update, but for
237        // testing we just use the Render schedule directly.
238        sub_app.update_schedule = Some(MySchedule.intern());
239
240        sub_app.world_mut().add_observer(
241            |event: On<Add<(RenderComponent, RenderComponentExtra)>>, mut commands: Commands| {
242                // Simulate data that's not extracted
243                commands
244                    .entity(event.entity)
245                    .insert(RenderComponentNoExtract);
246            },
247        );
248
249        app.update();
250
251        // Check that all components have been extracted
252        {
253            let sub_app = app.get_sub_app_mut(ExtractApp).unwrap();
254            sub_app
255                .world_mut()
256                .run_system_cached(
257                    |entity: Single<(
258                        &MainEntity,
259                        Option<&RenderComponent>,
260                        Option<&RenderComponentExtra>,
261                        Option<&RenderComponentSeparate>,
262                        Option<&RenderComponentNoExtract>,
263                    )>| {
264                        assert!(entity.1.is_some());
265                        assert!(entity.2.is_some());
266                        assert!(entity.3.is_some());
267                        assert!(entity.4.is_some());
268                    },
269                )
270                .unwrap();
271        }
272
273        // Remove RenderComponent
274        app.world_mut()
275            .run_system_cached(
276                |mut commands: Commands, query: Query<Entity, With<RenderComponent>>| {
277                    for entity in query {
278                        commands.entity(entity).remove::<RenderComponent>();
279                    }
280                },
281            )
282            .unwrap();
283
284        app.update();
285
286        // Check that the extracted components have been removed
287        {
288            let sub_app = app.get_sub_app_mut(ExtractApp).unwrap();
289            sub_app
290                .world_mut()
291                .run_system_cached(
292                    |entity: Single<(
293                        &MainEntity,
294                        Option<&RenderComponent>,
295                        Option<&RenderComponentExtra>,
296                        Option<&RenderComponentSeparate>,
297                        Option<&RenderComponentNoExtract>,
298                    )>| {
299                        assert!(entity.1.is_none());
300                        assert!(entity.2.is_none());
301                        assert!(entity.3.is_some());
302                        assert!(entity.4.is_some());
303                    },
304                )
305                .unwrap();
306        }
307    }
308
309    #[derive(AppLabel, Debug, Hash, PartialEq, Eq, Clone, Default, Copy)]
310    pub struct ExtractAppA;
311
312    #[derive(AppLabel, Debug, Hash, PartialEq, Eq, Clone, Default, Copy)]
313    pub struct ExtractAppB;
314
315    #[derive(Component, Clone, Debug)]
316    struct RenderComponentSeparateA;
317
318    #[derive(Component, Clone, Debug)]
319    struct RenderComponentSeparateB;
320
321    #[derive(Component, Clone, Debug)]
322    struct RenderComponentSeparateBoth;
323
324    #[derive(Component, Clone, Debug, ExtractComponent)]
325    #[extract_app(ExtractAppA, ExtractAppB)]
326    struct RenderComponentDual;
327
328    impl SyncComponent<ExtractAppA> for RenderComponentSeparateA {
329        type Target = RenderComponentSeparateA;
330    }
331
332    impl ExtractComponent<ExtractAppA> for RenderComponentSeparateA {
333        type QueryData = &'static Self;
334        type QueryFilter = ();
335        type Out = Self;
336
337        fn extract_component(
338            _item: bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>,
339        ) -> Option<Self::Out> {
340            Some(RenderComponentSeparateA)
341        }
342    }
343
344    impl SyncComponent<ExtractAppB> for RenderComponentSeparateB {
345        type Target = RenderComponentSeparateB;
346    }
347
348    impl ExtractComponent<ExtractAppB> for RenderComponentSeparateB {
349        type QueryData = &'static Self;
350        type QueryFilter = ();
351        type Out = Self;
352
353        fn extract_component(
354            _item: bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>,
355        ) -> Option<Self::Out> {
356            Some(RenderComponentSeparateB)
357        }
358    }
359
360    impl SyncComponent<ExtractAppA> for RenderComponentSeparateBoth {
361        type Target = RenderComponentSeparateBoth;
362    }
363
364    impl ExtractComponent<ExtractAppA> for RenderComponentSeparateBoth {
365        type QueryData = &'static Self;
366        type QueryFilter = ();
367        type Out = Self;
368
369        fn extract_component(
370            _item: bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>,
371        ) -> Option<Self::Out> {
372            Some(RenderComponentSeparateBoth)
373        }
374    }
375
376    impl SyncComponent<ExtractAppB> for RenderComponentSeparateBoth {
377        type Target = RenderComponentSeparateBoth;
378    }
379
380    impl ExtractComponent<ExtractAppB> for RenderComponentSeparateBoth {
381        type QueryData = &'static Self;
382        type QueryFilter = ();
383        type Out = Self;
384
385        fn extract_component(
386            _item: bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>,
387        ) -> Option<Self::Out> {
388            Some(RenderComponentSeparateBoth)
389        }
390    }
391
392    #[test]
393    fn dual_extraction_works() {
394        let mut app = App::new();
395
396        app.add_plugins(ExtractPlugin::<ExtractAppA>::new(
397            |_, _| {},
398            MySchedule::base_schedule,
399            MySchedule.intern(),
400            MyScheduleSystems::ExtractCommands.intern(),
401            MyScheduleSystems::PostCleanup.intern(),
402        ));
403        app.add_plugins(ExtractPlugin::<ExtractAppB>::new(
404            |_, _| {},
405            MySchedule::base_schedule,
406            MySchedule.intern(),
407            MyScheduleSystems::ExtractCommands.intern(),
408            MyScheduleSystems::PostCleanup.intern(),
409        ));
410
411        app.add_plugins(ExtractComponentPlugin::<
412            RenderComponentSeparateA,
413            ExtractAppA,
414        >::default());
415        app.add_plugins(ExtractComponentPlugin::<
416            RenderComponentSeparateB,
417            ExtractAppB,
418        >::default());
419        app.add_plugins(ExtractComponentPlugin::<
420            RenderComponentSeparateBoth,
421            ExtractAppA,
422        >::default());
423        app.add_plugins(ExtractComponentPlugin::<
424            RenderComponentSeparateBoth,
425            ExtractAppB,
426        >::default());
427        app.add_plugins(ExtractComponentPlugin::<RenderComponentDual, ExtractAppA>::default());
428        app.add_plugins(ExtractComponentPlugin::<RenderComponentDual, ExtractAppB>::default());
429
430        app.add_systems(Startup, |mut commands: Commands| {
431            commands.spawn((
432                RenderComponentSeparateA,
433                RenderComponentSeparateB,
434                RenderComponentSeparateBoth,
435                RenderComponentDual,
436            ));
437        });
438
439        let sub_app_a = app.get_sub_app_mut(ExtractAppA).unwrap();
440        sub_app_a.update_schedule = Some(MySchedule.intern());
441
442        let sub_app_b = app.get_sub_app_mut(ExtractAppB).unwrap();
443        sub_app_b.update_schedule = Some(MySchedule.intern());
444
445        app.update();
446
447        // Check that all components have been extracted
448        {
449            let sub_app_a = app.get_sub_app_mut(ExtractAppA).unwrap();
450            sub_app_a
451                .world_mut()
452                .run_system_cached(
453                    |entity: Single<(
454                        &MainEntity,
455                        Option<&RenderComponentSeparateA>,
456                        Option<&RenderComponentSeparateB>,
457                        Option<&RenderComponentSeparateBoth>,
458                        Option<&RenderComponentDual>,
459                    )>| {
460                        assert!(entity.1.is_some());
461                        assert!(entity.2.is_none());
462                        assert!(entity.3.is_some());
463                        assert!(entity.4.is_some());
464                    },
465                )
466                .unwrap();
467        }
468
469        {
470            let sub_app_b = app.get_sub_app_mut(ExtractAppB).unwrap();
471            sub_app_b
472                .world_mut()
473                .run_system_cached(
474                    |entity: Single<(
475                        &MainEntity,
476                        Option<&RenderComponentSeparateA>,
477                        Option<&RenderComponentSeparateB>,
478                        Option<&RenderComponentSeparateBoth>,
479                        Option<&RenderComponentDual>,
480                    )>| {
481                        assert!(entity.1.is_none());
482                        assert!(entity.2.is_some());
483                        assert!(entity.3.is_some());
484                        assert!(entity.4.is_some());
485                    },
486                )
487                .unwrap();
488        }
489
490        // Remove RenderComponentSeparateA
491        app.world_mut()
492            .run_system_cached(
493                |mut commands: Commands, query: Query<Entity, With<RenderComponentSeparateA>>| {
494                    for entity in query {
495                        commands.entity(entity).remove::<RenderComponentSeparateA>();
496                    }
497                },
498            )
499            .unwrap();
500
501        app.update();
502
503        // Check that the extracted components have been removed
504        {
505            let sub_app_a = app.get_sub_app_mut(ExtractAppA).unwrap();
506            sub_app_a
507                .world_mut()
508                .run_system_cached(
509                    |entity: Single<(
510                        &MainEntity,
511                        Option<&RenderComponentSeparateA>,
512                        Option<&RenderComponentSeparateB>,
513                        Option<&RenderComponentSeparateBoth>,
514                        Option<&RenderComponentDual>,
515                    )>| {
516                        assert!(entity.1.is_none());
517                        assert!(entity.2.is_none());
518                        assert!(entity.3.is_some());
519                        assert!(entity.4.is_some());
520                    },
521                )
522                .unwrap();
523        }
524
525        {
526            let sub_app_b = app.get_sub_app_mut(ExtractAppB).unwrap();
527            sub_app_b
528                .world_mut()
529                .run_system_cached(
530                    |entity: Single<(
531                        &MainEntity,
532                        Option<&RenderComponentSeparateA>,
533                        Option<&RenderComponentSeparateB>,
534                        Option<&RenderComponentSeparateBoth>,
535                        Option<&RenderComponentDual>,
536                    )>| {
537                        assert!(entity.1.is_none());
538                        assert!(entity.2.is_some());
539                        assert!(entity.3.is_some());
540                        assert!(entity.4.is_some());
541                    },
542                )
543                .unwrap();
544        }
545    }
546}