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
16pub struct ExtractPlugin<L: AppLabel + Default> {
19 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 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 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 extract(main_world, sub_world);
94 });
95
96 app.insert_sub_app(L::default(), sub_app);
97 }
98}
99
100#[derive(ScheduleLabel, PartialEq, Eq, Debug, Clone, Hash, Default)]
108pub struct ExtractSchedule;
109
110fn 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#[derive(Resource, Default, Deref, DerefMut)]
127pub struct MainWorld(World);
128
129#[derive(Resource, Default)]
132struct ScratchMainWorld(World);
133
134pub fn extract(main_world: &mut World, sub_world: &mut World) {
137 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 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 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 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 commands
244 .entity(event.entity)
245 .insert(RenderComponentNoExtract);
246 },
247 );
248
249 app.update();
250
251 {
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 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 {
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 {
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 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 {
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}