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#[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#[derive(Component, Copy, Clone, Debug, Default)]
122#[component(storage = "SparseSet")]
123pub struct SyncToSubWorld<L: AppLabel + Default + Clone>(PhantomData<L>);
124
125#[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
151unsafe impl<L: AppLabel + Clone + Copy + Eq> EntityEquivalent for SubEntity<L> {}
153
154#[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
179unsafe impl EntityEquivalent for MainEntity {}
181
182pub type MainEntityHashMap<V> = EntityEquivalentHashMap<MainEntity, V>;
187
188pub type MainEntityHashSet = EntityEquivalentHashSet<MainEntity>;
193
194#[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#[derive(Debug)]
201pub(crate) enum EntityRecord<L: AppLabel + Clone + Copy + Eq> {
202 Added(Entity),
205 Removed(SubEntity<L>),
208 ComponentRemoved(Entity, fn(EntityWorldMut<'_>)),
211}
212
213#[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 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 local.sort_unstable_by_key(|e| e.index());
272 for e in local.drain(..).rev() {
273 world.despawn(e);
274 }
275}
276
277mod 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 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 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 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 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 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 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 unsafe impl<L: AppLabel + Clone + Copy + Eq> IterQueryData for SubEntity<L> {}
414
415 unsafe impl<L: AppLabel + Clone + Copy + Eq> ReadOnlyQueryData for SubEntity<L> {}
417
418 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 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 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 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 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 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 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 unsafe impl IterQueryData for MainEntity {}
545
546 unsafe impl ReadOnlyQueryData for MainEntity {}
548
549 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 for _ in 0..99 {
610 main_world.spawn_empty();
611 }
612
613 let main_entity = main_world
615 .spawn(SubDataComponent)
616 .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 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 main_world.despawn(main_entity);
642
643 entity_sync_system::<ExtractApp>(&mut main_world, &mut sub_world);
644
645 assert!(q.iter(&sub_world).count() == 0);
647 }
648}