Skip to main content

bevy_extract/
sync_world.rs

1use core::marker::PhantomData;
2
3use bevy_app::{AppLabel, Plugin};
4use bevy_derive::{Deref, DerefMut};
5use bevy_ecs::entity::hash_map::EntityEquivalentHashMap;
6use bevy_ecs::entity::hash_set::EntityEquivalentHashSet;
7use bevy_ecs::{
8    component::Component,
9    entity::{ContainsEntity, Entity, EntityEquivalent},
10    lifecycle::{Add, Remove},
11    observer::On,
12    query::With,
13    reflect::ReflectComponent,
14    resource::Resource,
15    system::{Local, Query, ResMut, SystemState},
16    world::{EntityWorldMut, Mut, World},
17};
18use bevy_reflect::{std_traits::ReflectDefault, Reflect};
19
20/// A plugin that synchronizes entities with [`SyncToSubWorld`] between the main world and the sub world.
21///
22/// All entities with the [`SyncToSubWorld`] component are kept in sync. It
23/// is automatically added as a required component by [`ExtractComponentPlugin`]
24/// and [`SyncComponentPlugin`], so it doesn't need to be added manually when
25/// spawning or as a required component when either of these plugins are used.
26///
27/// # Implementation
28///
29/// [`SyncWorldPlugin`] is the first thing that runs every frame and it maintains an entity-to-entity mapping
30/// between the main world and the sub world.
31/// It does so by spawning and despawning entities in the sub world, to match spawned and despawned entities in the main world.
32/// The link between synced entities is maintained by the [`SubEntity`] and [`MainEntity`] components.
33///
34/// The [`SubEntity`] contains the corresponding sub world entity of a main world entity, while [`MainEntity`] contains
35/// the corresponding main world entity of a sub world entity.
36/// For convenience, [`QueryData`](bevy_ecs::query::QueryData) implementations are provided for both components:
37/// adding [`MainEntity`] to a query (without a `&`) will return the corresponding main world [`Entity`],
38/// and adding [`SubEntity`] will return the corresponding sub world [`Entity`].
39/// If you have access to the component itself, the underlying entities can be accessed by calling `.id()`.
40///
41/// Synchronization is necessary preparation for extraction ([`ExtractSchedule`](crate::ExtractSchedule)), which copies over component data from the main
42/// to the sub world for these entities.
43///
44/// ```text
45/// |--------------------------------------------------------------------|
46/// |      |         |          Main world update                        |
47/// | sync | extract |---------------------------------------------------|
48/// |      |         |         Sub world update                       |
49/// |--------------------------------------------------------------------|
50/// ```
51///
52/// An example for synchronized main entities 1v1 and 18v1
53///
54/// ```text
55/// |---------------------------Main World------------------------------|
56/// |  Entity  |                    Component                           |
57/// |-------------------------------------------------------------------|
58/// | ID: 1v1  | PointLight | SubEntity(ID: 3V1) | SyncToSubWorld |
59/// | ID: 18v1 | PointLight | SubEntity(ID: 5V1) | SyncToSubWorld |
60/// |-------------------------------------------------------------------|
61///
62/// |----------Sub world-----------|
63/// |  Entity  |       Component      |
64/// |---------------------------------|
65/// | ID: 3v1  | MainEntity(ID: 1V1)  |
66/// | ID: 5v1  | MainEntity(ID: 18V1) |
67/// |---------------------------------|
68///
69/// ```
70///
71/// Note that this effectively establishes a link between the main world entity and the sub world entity.
72/// Not every entity needs to be synchronized, however; only entities with the [`SyncToSubWorld`] component are synced.
73/// Adding [`SyncToSubWorld`] to a main world component will establish such a link.
74/// Once a synchronized main entity is despawned, its corresponding sub entity will be automatically
75/// despawned in the next `sync`.
76///
77/// The sync step does not copy any of component data between worlds, since its often not necessary to transfer over all
78/// the components of a main world entity.
79/// A sub world may care about a `Position` component, but may not care about a `Velocity` component.
80/// The extraction happens in its own step, independently from, and after synchronization.
81///
82/// Moreover, [`SyncWorldPlugin`] only synchronizes *entities*. [`Asset`]s like meshes and textures are handled
83/// differently.
84///
85/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin
86/// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin
87/// [`Asset`]: https://docs.rs/bevy/latest/bevy/asset/trait.Asset.html
88#[derive(Default)]
89pub struct SyncWorldPlugin<L: AppLabel>(PhantomData<L>);
90
91impl<L: AppLabel + Default + Clone + Copy + Eq> Plugin for SyncWorldPlugin<L> {
92    fn build(&self, app: &mut bevy_app::App) {
93        app.init_resource::<PendingSyncEntity<L>>();
94        app.add_observer(
95            |add: On<Add<SyncToSubWorld<L>>>, mut pending: ResMut<PendingSyncEntity<L>>| {
96                pending.push(EntityRecord::<L>::Added(add.entity));
97            },
98        );
99        app.add_observer(
100            |remove: On<Remove<SyncToSubWorld<L>>>,
101             mut pending: ResMut<PendingSyncEntity<L>>,
102             query: Query<&SubEntity<L>>| {
103                if let Ok(e) = query.get(remove.entity) {
104                    pending.push(EntityRecord::<L>::Removed(*e));
105                };
106            },
107        );
108    }
109}
110
111/// Marker component that indicates that its entity needs to be synchronized to the sub world.
112///
113/// This component is automatically added as a required component by [`ExtractComponentPlugin`] and [`SyncComponentPlugin`].
114/// For more information see [`SyncWorldPlugin`].
115///
116/// NOTE: This component should persist throughout the entity's entire lifecycle.
117/// If this component is removed from its entity, the entity will be despawned.
118///
119/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin
120/// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin
121#[derive(Component, Copy, Clone, Debug, Default)]
122#[component(storage = "SparseSet")]
123pub struct SyncToSubWorld<L: AppLabel + Default + Clone>(PhantomData<L>);
124
125/// Component added on the main world entities that are synced to the Sub World in order to keep track of the corresponding sub world entity.
126///
127/// Can also be used as a newtype wrapper for sub world entities.
128#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq)]
129#[component(clone_behavior = Ignore)]
130pub struct SubEntity<L: AppLabel + Clone + Copy + Eq>(#[deref] Entity, PhantomData<L>);
131
132impl<L: AppLabel + Clone + Copy + Eq> SubEntity<L> {
133    #[inline]
134    pub fn id(&self) -> Entity {
135        self.0
136    }
137}
138
139impl<L: AppLabel + Clone + Copy + Eq> From<Entity> for SubEntity<L> {
140    fn from(entity: Entity) -> Self {
141        SubEntity(entity, PhantomData)
142    }
143}
144
145impl<L: AppLabel + Clone + Copy + Eq> ContainsEntity for SubEntity<L> {
146    fn entity(&self) -> Entity {
147        self.id()
148    }
149}
150
151// SAFETY: SubEntity is a newtype around Entity that derives its comparison traits.
152unsafe impl<L: AppLabel + Clone + Copy + Eq> EntityEquivalent for SubEntity<L> {}
153
154/// Component added on the sub world entities to keep track of the corresponding main world entity.
155///
156/// Can also be used as a newtype wrapper for main world entities.
157#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Reflect)]
158#[reflect(Component, Clone)]
159pub struct MainEntity(Entity);
160impl MainEntity {
161    #[inline]
162    pub fn id(&self) -> Entity {
163        self.0
164    }
165}
166
167impl From<Entity> for MainEntity {
168    fn from(entity: Entity) -> Self {
169        MainEntity(entity)
170    }
171}
172
173impl ContainsEntity for MainEntity {
174    fn entity(&self) -> Entity {
175        self.id()
176    }
177}
178
179// SAFETY: MainEntity is a newtype around Entity that derives its comparison traits.
180unsafe impl EntityEquivalent for MainEntity {}
181
182/// A [`HashMap`] pre-configured to use [`EntityHash`] hashing with a [`MainEntity`].
183///
184/// [`HashMap`]: `bevy_ecs::entity::hash_map::EntityEquivalentHashMap`
185/// [`EntityHash`]: `bevy_ecs::entity::EntityHash`
186pub type MainEntityHashMap<V> = EntityEquivalentHashMap<MainEntity, V>;
187
188/// A [`HashSet`] pre-configured to use [`EntityHash`] hashing with a [`MainEntity`].
189///
190/// [`HashSet`]: `bevy_ecs::entity::hash_set::EntityEquivalentHashSet`
191/// [`EntityHash`]: `bevy_ecs::entity::EntityHash`
192pub type MainEntityHashSet = EntityEquivalentHashSet<MainEntity>;
193
194/// Marker component that indicates that its entity needs to be despawned at the end of the frame.
195#[derive(Component, Copy, Clone, Debug, Default, Reflect)]
196#[reflect(Component, Default, Clone)]
197pub struct TemporaryEntity<L: AppLabel + Clone + Eq + Copy + Default>(PhantomData<L>);
198
199/// A record enum to what entities with [`SyncToSubWorld`] have been added or removed.
200#[derive(Debug)]
201pub(crate) enum EntityRecord<L: AppLabel + Clone + Copy + Eq> {
202    /// When an entity is spawned on the main world, notify the sub world so that it can spawn a corresponding
203    /// entity. This contains the main world entity.
204    Added(Entity),
205    /// When an entity is despawned on the main world, notify the sub world so that the corresponding entity can be
206    /// despawned. This contains the sub world entity.
207    Removed(SubEntity<L>),
208    /// When a component is removed from an entity, notify the sub world so that the corresponding component can be
209    /// removed. This contains the main world entity.
210    ComponentRemoved(Entity, fn(EntityWorldMut<'_>)),
211}
212
213// Entity Record in MainWorld pending to Sync
214#[derive(Resource, Default, Deref, DerefMut)]
215pub(crate) struct PendingSyncEntity<L: AppLabel + Clone + Copy + Eq> {
216    #[deref]
217    records: Vec<EntityRecord<L>>,
218    marker: PhantomData<L>,
219}
220
221pub(crate) fn entity_sync_system<L: AppLabel + Clone + Copy + Eq>(
222    main_world: &mut World,
223    sub_world: &mut World,
224) {
225    main_world.resource_scope(|world, mut pending: Mut<PendingSyncEntity<L>>| {
226        // TODO : batching record
227        for record in pending.drain(..) {
228            match record {
229                EntityRecord::Added(e) => {
230                    if let Ok(mut main_entity) = world.get_entity_mut(e) {
231                        match main_entity.entry::<SubEntity<L>>() {
232                            bevy_ecs::world::ComponentEntry::Occupied(_) => {
233                                panic!("Attempting to synchronize an entity that has already been synchronized!");
234                            }
235                            bevy_ecs::world::ComponentEntry::Vacant(entry) => {
236                                let id = sub_world.spawn(MainEntity(e)).id();
237
238                                entry.insert(SubEntity::<L>(id, PhantomData));
239                            }
240                        };
241                    }
242                }
243                EntityRecord::Removed(sub_entity) => {
244                    if let Ok(ec) = sub_world.get_entity_mut(sub_entity.id()) {
245                        ec.despawn();
246                    };
247                }
248                EntityRecord::ComponentRemoved(main_entity, removal_function) => {
249                    let Some(sub_entity) = world.get::<SubEntity<L>>(main_entity) else {
250                        continue;
251                    };
252                    if let Ok(sub_world_entity) = sub_world.get_entity_mut(sub_entity.id()) {
253                        removal_function(sub_world_entity);
254                    }
255                },
256            }
257        }
258    });
259}
260
261pub(crate) fn despawn_temporary_entities<L: AppLabel + Copy + Default + Eq>(
262    world: &mut World,
263    state: &mut SystemState<Query<Entity, With<TemporaryEntity<L>>>>,
264    mut local: Local<Vec<Entity>>,
265) {
266    let query = state.get(world).unwrap();
267
268    local.extend(query.iter());
269
270    // Ensure next frame allocation keeps order
271    local.sort_unstable_by_key(|e| e.index());
272    for e in local.drain(..).rev() {
273        world.despawn(e);
274    }
275}
276
277/// This module exists to keep the complex unsafe code out of the main module.
278///
279/// The implementations for both [`MainEntity`] and [`SubEntity`] should stay in sync,
280/// and are based off of the `&T` implementation in `bevy_ecs`.
281mod sub_entities_world_query_impls {
282    use super::{MainEntity, SubEntity};
283
284    use bevy_app::AppLabel;
285    use bevy_ecs::{
286        archetype::Archetype,
287        change_detection::Tick,
288        component::{ComponentId, Components},
289        entity::Entity,
290        query::{
291            ArchetypeQueryData, FilteredAccess, FilteredAccessSet, IterQueryData, QueryData,
292            ReadOnlyQueryData, ReleaseStateQueryData, SingleEntityQueryData, WorldQuery,
293        },
294        storage::{Table, TableRow},
295        world::{unsafe_world_cell::UnsafeWorldCell, World},
296    };
297
298    // SAFETY: defers completely to `&SubEntity` implementation,
299    // and then only modifies the output safely.
300    unsafe impl<L: AppLabel + Clone + Copy + Eq> WorldQuery for SubEntity<L> {
301        type Fetch<'w> = <&'static SubEntity<L> as WorldQuery>::Fetch<'w>;
302        type State = <&'static SubEntity<L> as WorldQuery>::State;
303
304        fn shrink_fetch<'wlong: 'wshort, 'wshort>(
305            fetch: Self::Fetch<'wlong>,
306        ) -> Self::Fetch<'wshort> {
307            fetch
308        }
309
310        #[inline]
311        unsafe fn init_fetch<'w, 's>(
312            world: UnsafeWorldCell<'w>,
313            component_id: &'s ComponentId,
314            last_run: Tick,
315            this_run: Tick,
316        ) -> Self::Fetch<'w> {
317            // SAFETY: defers to the `&T` implementation, with T set to `SubEntity`.
318            unsafe {
319                <&SubEntity<L> as WorldQuery>::init_fetch(world, component_id, last_run, this_run)
320            }
321        }
322
323        const IS_DENSE: bool = <&'static SubEntity<L> as WorldQuery>::IS_DENSE;
324
325        #[inline]
326        unsafe fn set_archetype<'w, 's>(
327            fetch: &mut Self::Fetch<'w>,
328            component_id: &'s ComponentId,
329            archetype: &'w Archetype,
330            table: &'w Table,
331        ) {
332            // SAFETY: defers to the `&T` implementation, with T set to `SubEntity`.
333            unsafe {
334                <&SubEntity<L> as WorldQuery>::set_archetype(fetch, component_id, archetype, table);
335            }
336        }
337
338        #[inline]
339        unsafe fn set_table<'w, 's>(
340            fetch: &mut Self::Fetch<'w>,
341            &component_id: &'s ComponentId,
342            table: &'w Table,
343        ) {
344            // SAFETY: defers to the `&T` implementation, with T set to `SubEntity`.
345            unsafe { <&SubEntity<L> as WorldQuery>::set_table(fetch, &component_id, table) }
346        }
347
348        fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) {
349            <&SubEntity<L> as WorldQuery>::update_component_access(&component_id, access);
350        }
351
352        fn init_nested_access(
353            _state: &Self::State,
354            _system_name: Option<&str>,
355            _component_access_set: &mut FilteredAccessSet,
356            _world: UnsafeWorldCell,
357        ) {
358        }
359
360        fn init_state(world: &mut World) -> ComponentId {
361            <&SubEntity<L> as WorldQuery>::init_state(world)
362        }
363
364        fn get_state(components: &Components) -> Option<Self::State> {
365            <&SubEntity<L> as WorldQuery>::get_state(components)
366        }
367
368        fn matches_component_set(
369            &state: &ComponentId,
370            set_contains_id: &impl Fn(ComponentId) -> bool,
371        ) -> bool {
372            <&SubEntity<L> as WorldQuery>::matches_component_set(&state, set_contains_id)
373        }
374
375        fn update_archetypes(_state: &mut Self::State, _world: UnsafeWorldCell) {}
376    }
377
378    // SAFETY: Component access of Self::ReadOnly is a subset of Self.
379    // Self::ReadOnly matches exactly the same archetypes/tables as Self.
380    unsafe impl<L: AppLabel + Clone + Copy + Eq> QueryData for SubEntity<L> {
381        const IS_READ_ONLY: bool = true;
382        const IS_ARCHETYPAL: bool = <&MainEntity as QueryData>::IS_ARCHETYPAL;
383        type ReadOnly = SubEntity<L>;
384        type Item<'w, 's> = Entity;
385
386        fn shrink<'wlong: 'wshort, 'wshort, 's>(
387            item: Self::Item<'wlong, 's>,
388        ) -> Self::Item<'wshort, 's> {
389            item
390        }
391
392        #[inline(always)]
393        unsafe fn fetch<'w, 's>(
394            state: &'s Self::State,
395            fetch: &mut Self::Fetch<'w>,
396            entity: Entity,
397            table_row: TableRow,
398        ) -> Option<Self::Item<'w, 's>> {
399            // SAFETY: defers to the `&T` implementation, with T set to `SubEntity`.
400            let component =
401                unsafe { <&SubEntity<L> as QueryData>::fetch(state, fetch, entity, table_row) };
402            component.map(SubEntity::id)
403        }
404
405        fn iter_access(
406            state: &Self::State,
407        ) -> impl Iterator<Item = bevy_ecs::query::EcsAccessType<'_>> {
408            <&SubEntity<L> as QueryData>::iter_access(state)
409        }
410    }
411
412    /// SAFETY: access is read only and only on the current entity
413    unsafe impl<L: AppLabel + Clone + Copy + Eq> IterQueryData for SubEntity<L> {}
414
415    /// SAFETY: access is read only
416    unsafe impl<L: AppLabel + Clone + Copy + Eq> ReadOnlyQueryData for SubEntity<L> {}
417
418    /// SAFETY: access is only on the current entity
419    unsafe impl<L: AppLabel + Clone + Copy + Eq> SingleEntityQueryData for SubEntity<L> {}
420
421    impl<L: AppLabel + Clone + Copy + Eq> ArchetypeQueryData for SubEntity<L> {}
422
423    impl<L: AppLabel + Clone + Copy + Eq> ReleaseStateQueryData for SubEntity<L> {
424        fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> {
425            item
426        }
427    }
428
429    // SAFETY: defers completely to `&SubEntity` implementation,
430    // and then only modifies the output safely.
431    unsafe impl WorldQuery for MainEntity {
432        type Fetch<'w> = <&'static MainEntity as WorldQuery>::Fetch<'w>;
433        type State = <&'static MainEntity as WorldQuery>::State;
434
435        fn shrink_fetch<'wlong: 'wshort, 'wshort>(
436            fetch: Self::Fetch<'wlong>,
437        ) -> Self::Fetch<'wshort> {
438            fetch
439        }
440
441        #[inline]
442        unsafe fn init_fetch<'w, 's>(
443            world: UnsafeWorldCell<'w>,
444            component_id: &'s ComponentId,
445            last_run: Tick,
446            this_run: Tick,
447        ) -> Self::Fetch<'w> {
448            // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`.
449            unsafe {
450                <&MainEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run)
451            }
452        }
453
454        const IS_DENSE: bool = <&'static MainEntity as WorldQuery>::IS_DENSE;
455
456        #[inline]
457        unsafe fn set_archetype<'w, 's>(
458            fetch: &mut Self::Fetch<'w>,
459            component_id: &ComponentId,
460            archetype: &'w Archetype,
461            table: &'w Table,
462        ) {
463            // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`.
464            unsafe {
465                <&MainEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table);
466            }
467        }
468
469        #[inline]
470        unsafe fn set_table<'w, 's>(
471            fetch: &mut Self::Fetch<'w>,
472            &component_id: &'s ComponentId,
473            table: &'w Table,
474        ) {
475            // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`.
476            unsafe { <&MainEntity as WorldQuery>::set_table(fetch, &component_id, table) }
477        }
478
479        fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) {
480            <&MainEntity as WorldQuery>::update_component_access(&component_id, access);
481        }
482
483        fn init_nested_access(
484            _state: &Self::State,
485            _system_name: Option<&str>,
486            _component_access_set: &mut FilteredAccessSet,
487            _world: UnsafeWorldCell,
488        ) {
489        }
490
491        fn init_state(world: &mut World) -> ComponentId {
492            <&MainEntity as WorldQuery>::init_state(world)
493        }
494
495        fn get_state(components: &Components) -> Option<Self::State> {
496            <&MainEntity as WorldQuery>::get_state(components)
497        }
498
499        fn matches_component_set(
500            &state: &ComponentId,
501            set_contains_id: &impl Fn(ComponentId) -> bool,
502        ) -> bool {
503            <&MainEntity as WorldQuery>::matches_component_set(&state, set_contains_id)
504        }
505
506        fn update_archetypes(_state: &mut Self::State, _world: UnsafeWorldCell) {}
507    }
508
509    // SAFETY: Component access of Self::ReadOnly is a subset of Self.
510    // Self::ReadOnly matches exactly the same archetypes/tables as Self.
511    unsafe impl QueryData for MainEntity {
512        const IS_READ_ONLY: bool = true;
513        const IS_ARCHETYPAL: bool = <&MainEntity as QueryData>::IS_ARCHETYPAL;
514        type ReadOnly = MainEntity;
515        type Item<'w, 's> = Entity;
516
517        fn shrink<'wlong: 'wshort, 'wshort, 's>(
518            item: Self::Item<'wlong, 's>,
519        ) -> Self::Item<'wshort, 's> {
520            item
521        }
522
523        #[inline(always)]
524        unsafe fn fetch<'w, 's>(
525            state: &'s Self::State,
526            fetch: &mut Self::Fetch<'w>,
527            entity: Entity,
528            table_row: TableRow,
529        ) -> Option<Self::Item<'w, 's>> {
530            // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`.
531            let component =
532                unsafe { <&MainEntity as QueryData>::fetch(state, fetch, entity, table_row) };
533            component.map(MainEntity::id)
534        }
535
536        fn iter_access(
537            state: &Self::State,
538        ) -> impl Iterator<Item = bevy_ecs::query::EcsAccessType<'_>> {
539            <&MainEntity as QueryData>::iter_access(state)
540        }
541    }
542
543    /// SAFETY: access is read only and only on the current entity
544    unsafe impl IterQueryData for MainEntity {}
545
546    /// SAFETY: access is read only
547    unsafe impl ReadOnlyQueryData for MainEntity {}
548
549    /// SAFETY: access is only on the current entity
550    unsafe impl SingleEntityQueryData for MainEntity {}
551
552    impl ArchetypeQueryData for MainEntity {}
553
554    impl ReleaseStateQueryData for MainEntity {
555        fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> {
556            item
557        }
558    }
559}
560
561#[cfg(test)]
562mod tests {
563    use core::marker::PhantomData;
564
565    use bevy_derive::AppLabel;
566    use bevy_ecs::{
567        component::Component,
568        entity::Entity,
569        lifecycle::{Add, Remove},
570        observer::On,
571        query::With,
572        system::{Query, ResMut},
573        world::World,
574    };
575
576    use super::{
577        entity_sync_system, EntityRecord, MainEntity, PendingSyncEntity, SubEntity, SyncToSubWorld,
578    };
579
580    #[derive(Component)]
581    struct SubDataComponent;
582
583    #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
584    struct ExtractApp;
585
586    #[test]
587    fn sync_world() {
588        let mut main_world = World::new();
589        let mut sub_world = World::new();
590        main_world.init_resource::<PendingSyncEntity<ExtractApp>>();
591
592        main_world.add_observer(
593            |add: On<Add<SyncToSubWorld<ExtractApp>>>,
594             mut pending: ResMut<PendingSyncEntity<ExtractApp>>| {
595                pending.push(EntityRecord::Added(add.entity));
596            },
597        );
598        main_world.add_observer(
599            |remove: On<Remove<SyncToSubWorld<ExtractApp>>>,
600             mut pending: ResMut<PendingSyncEntity<ExtractApp>>,
601             query: Query<&SubEntity<ExtractApp>>| {
602                if let Ok(e) = query.get(remove.entity) {
603                    pending.push(EntityRecord::Removed(*e));
604                };
605            },
606        );
607
608        // spawn some empty entities for test
609        for _ in 0..99 {
610            main_world.spawn_empty();
611        }
612
613        // spawn
614        let main_entity = main_world
615            .spawn(SubDataComponent)
616            // indicates that its entity needs to be synchronized to the sub world
617            .insert(SyncToSubWorld::<ExtractApp>(PhantomData))
618            .id();
619
620        entity_sync_system::<ExtractApp>(&mut main_world, &mut sub_world);
621
622        let mut q = sub_world.query_filtered::<Entity, With<MainEntity>>();
623
624        // Only one synchronized entity
625        assert!(q.iter(&sub_world).count() == 1);
626
627        let sub_entity = q.single(&sub_world).unwrap();
628        let sub_entity_component = main_world
629            .get::<SubEntity<ExtractApp>>(main_entity)
630            .unwrap();
631
632        assert!(sub_entity_component.id() == sub_entity);
633
634        let main_entity_component = sub_world
635            .get::<MainEntity>(sub_entity_component.id())
636            .unwrap();
637
638        assert!(main_entity_component.id() == main_entity);
639
640        // despawn
641        main_world.despawn(main_entity);
642
643        entity_sync_system::<ExtractApp>(&mut main_world, &mut sub_world);
644
645        // Only one synchronized entity
646        assert!(q.iter(&sub_world).count() == 0);
647    }
648}