1use bevy_app::Plugin;
2use bevy_derive::{Deref, DerefMut};
3use bevy_ecs::{
4 component::Component,
5 entity::{ContainsEntity, Entity, EntityEquivalent, EntityHash},
6 lifecycle::{Add, Remove},
7 observer::On,
8 query::With,
9 reflect::ReflectComponent,
10 resource::Resource,
11 system::{Local, Query, ResMut, SystemState},
12 world::{Mut, World},
13};
14use bevy_platform::collections::{HashMap, HashSet};
15use bevy_reflect::{std_traits::ReflectDefault, Reflect};
16
17#[derive(Default)]
90pub struct SyncWorldPlugin;
91
92impl Plugin for SyncWorldPlugin {
93 fn build(&self, app: &mut bevy_app::App) {
94 app.init_resource::<PendingSyncEntity>();
95 app.add_observer(
96 |add: On<Add, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
97 pending.push(EntityRecord::Added(add.entity));
98 },
99 );
100 app.add_observer(
101 |remove: On<Remove, SyncToRenderWorld>,
102 mut pending: ResMut<PendingSyncEntity>,
103 query: Query<&RenderEntity>| {
104 if let Ok(e) = query.get(remove.entity) {
105 pending.push(EntityRecord::Removed(*e));
106 };
107 },
108 );
109 }
110}
111#[derive(Component, Copy, Clone, Debug, Default, Reflect)]
122#[reflect[Component, Default, Clone]]
123#[component(storage = "SparseSet")]
124pub struct SyncToRenderWorld;
125
126#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, Reflect)]
130#[component(clone_behavior = Ignore)]
131#[reflect(Component, Clone)]
132pub struct RenderEntity(Entity);
133impl RenderEntity {
134 #[inline]
135 pub fn id(&self) -> Entity {
136 self.0
137 }
138}
139
140impl From<Entity> for RenderEntity {
141 fn from(entity: Entity) -> Self {
142 RenderEntity(entity)
143 }
144}
145
146impl ContainsEntity for RenderEntity {
147 fn entity(&self) -> Entity {
148 self.id()
149 }
150}
151
152unsafe impl EntityEquivalent for RenderEntity {}
154
155#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Reflect)]
159#[reflect(Component, Clone)]
160pub struct MainEntity(Entity);
161impl MainEntity {
162 #[inline]
163 pub fn id(&self) -> Entity {
164 self.0
165 }
166}
167
168impl From<Entity> for MainEntity {
169 fn from(entity: Entity) -> Self {
170 MainEntity(entity)
171 }
172}
173
174impl ContainsEntity for MainEntity {
175 fn entity(&self) -> Entity {
176 self.id()
177 }
178}
179
180unsafe impl EntityEquivalent for MainEntity {}
182
183pub type MainEntityHashMap<V> = HashMap<MainEntity, V, EntityHash>;
185
186pub type MainEntityHashSet = HashSet<MainEntity, EntityHash>;
188
189#[derive(Component, Copy, Clone, Debug, Default, Reflect)]
191#[reflect(Component, Default, Clone)]
192pub struct TemporaryRenderEntity;
193
194#[derive(Debug)]
196pub(crate) enum EntityRecord {
197 Added(Entity),
200 Removed(RenderEntity),
203 ComponentRemoved(Entity),
206}
207
208#[derive(Resource, Default, Deref, DerefMut)]
210pub(crate) struct PendingSyncEntity {
211 records: Vec<EntityRecord>,
212}
213
214pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut World) {
215 main_world.resource_scope(|world, mut pending: Mut<PendingSyncEntity>| {
216 for record in pending.drain(..) {
218 match record {
219 EntityRecord::Added(e) => {
220 if let Ok(mut main_entity) = world.get_entity_mut(e) {
221 match main_entity.entry::<RenderEntity>() {
222 bevy_ecs::world::ComponentEntry::Occupied(_) => {
223 panic!("Attempting to synchronize an entity that has already been synchronized!");
224 }
225 bevy_ecs::world::ComponentEntry::Vacant(entry) => {
226 let id = render_world.spawn(MainEntity(e)).id();
227
228 entry.insert(RenderEntity(id));
229 }
230 };
231 }
232 }
233 EntityRecord::Removed(render_entity) => {
234 if let Ok(ec) = render_world.get_entity_mut(render_entity.id()) {
235 ec.despawn();
236 };
237 }
238 EntityRecord::ComponentRemoved(main_entity) => {
239 let Some(mut render_entity) = world.get_mut::<RenderEntity>(main_entity) else {
240 continue;
241 };
242 if let Ok(render_world_entity) = render_world.get_entity_mut(render_entity.id()) {
243 render_world_entity.despawn();
246
247 let id = render_world.spawn(MainEntity(main_entity)).id();
248 render_entity.0 = id;
249 }
250 },
251 }
252 }
253 });
254}
255
256pub(crate) fn despawn_temporary_render_entities(
257 world: &mut World,
258 state: &mut SystemState<Query<Entity, With<TemporaryRenderEntity>>>,
259 mut local: Local<Vec<Entity>>,
260) {
261 let query = state.get(world);
262
263 local.extend(query.iter());
264
265 local.sort_unstable_by_key(|e| e.index());
267 for e in local.drain(..).rev() {
268 world.despawn(e);
269 }
270}
271
272mod render_entities_world_query_impls {
277 use super::{MainEntity, RenderEntity};
278
279 use bevy_ecs::{
280 archetype::Archetype,
281 component::{ComponentId, Components, Tick},
282 entity::Entity,
283 query::{FilteredAccess, QueryData, ReadOnlyQueryData, ReleaseStateQueryData, WorldQuery},
284 storage::{Table, TableRow},
285 world::{unsafe_world_cell::UnsafeWorldCell, World},
286 };
287
288 unsafe impl WorldQuery for RenderEntity {
291 type Fetch<'w> = <&'static RenderEntity as WorldQuery>::Fetch<'w>;
292 type State = <&'static RenderEntity as WorldQuery>::State;
293
294 fn shrink_fetch<'wlong: 'wshort, 'wshort>(
295 fetch: Self::Fetch<'wlong>,
296 ) -> Self::Fetch<'wshort> {
297 fetch
298 }
299
300 #[inline]
301 unsafe fn init_fetch<'w, 's>(
302 world: UnsafeWorldCell<'w>,
303 component_id: &'s ComponentId,
304 last_run: Tick,
305 this_run: Tick,
306 ) -> Self::Fetch<'w> {
307 unsafe {
309 <&RenderEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run)
310 }
311 }
312
313 const IS_DENSE: bool = <&'static RenderEntity as WorldQuery>::IS_DENSE;
314
315 #[inline]
316 unsafe fn set_archetype<'w, 's>(
317 fetch: &mut Self::Fetch<'w>,
318 component_id: &'s ComponentId,
319 archetype: &'w Archetype,
320 table: &'w Table,
321 ) {
322 unsafe {
324 <&RenderEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table);
325 }
326 }
327
328 #[inline]
329 unsafe fn set_table<'w, 's>(
330 fetch: &mut Self::Fetch<'w>,
331 &component_id: &'s ComponentId,
332 table: &'w Table,
333 ) {
334 unsafe { <&RenderEntity as WorldQuery>::set_table(fetch, &component_id, table) }
336 }
337
338 fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) {
339 <&RenderEntity as WorldQuery>::update_component_access(&component_id, access);
340 }
341
342 fn init_state(world: &mut World) -> ComponentId {
343 <&RenderEntity as WorldQuery>::init_state(world)
344 }
345
346 fn get_state(components: &Components) -> Option<Self::State> {
347 <&RenderEntity as WorldQuery>::get_state(components)
348 }
349
350 fn matches_component_set(
351 &state: &ComponentId,
352 set_contains_id: &impl Fn(ComponentId) -> bool,
353 ) -> bool {
354 <&RenderEntity as WorldQuery>::matches_component_set(&state, set_contains_id)
355 }
356 }
357
358 unsafe impl QueryData for RenderEntity {
361 const IS_READ_ONLY: bool = true;
362 type ReadOnly = RenderEntity;
363 type Item<'w, 's> = Entity;
364
365 fn shrink<'wlong: 'wshort, 'wshort, 's>(
366 item: Self::Item<'wlong, 's>,
367 ) -> Self::Item<'wshort, 's> {
368 item
369 }
370
371 #[inline(always)]
372 unsafe fn fetch<'w, 's>(
373 state: &'s Self::State,
374 fetch: &mut Self::Fetch<'w>,
375 entity: Entity,
376 table_row: TableRow,
377 ) -> Self::Item<'w, 's> {
378 let component =
380 unsafe { <&RenderEntity as QueryData>::fetch(state, fetch, entity, table_row) };
381 component.id()
382 }
383 }
384
385 unsafe impl ReadOnlyQueryData for RenderEntity {}
387
388 impl ReleaseStateQueryData for RenderEntity {
389 fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> {
390 item
391 }
392 }
393
394 unsafe impl WorldQuery for MainEntity {
397 type Fetch<'w> = <&'static MainEntity as WorldQuery>::Fetch<'w>;
398 type State = <&'static MainEntity as WorldQuery>::State;
399
400 fn shrink_fetch<'wlong: 'wshort, 'wshort>(
401 fetch: Self::Fetch<'wlong>,
402 ) -> Self::Fetch<'wshort> {
403 fetch
404 }
405
406 #[inline]
407 unsafe fn init_fetch<'w, 's>(
408 world: UnsafeWorldCell<'w>,
409 component_id: &'s ComponentId,
410 last_run: Tick,
411 this_run: Tick,
412 ) -> Self::Fetch<'w> {
413 unsafe {
415 <&MainEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run)
416 }
417 }
418
419 const IS_DENSE: bool = <&'static MainEntity as WorldQuery>::IS_DENSE;
420
421 #[inline]
422 unsafe fn set_archetype<'w, 's>(
423 fetch: &mut Self::Fetch<'w>,
424 component_id: &ComponentId,
425 archetype: &'w Archetype,
426 table: &'w Table,
427 ) {
428 unsafe {
430 <&MainEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table);
431 }
432 }
433
434 #[inline]
435 unsafe fn set_table<'w, 's>(
436 fetch: &mut Self::Fetch<'w>,
437 &component_id: &'s ComponentId,
438 table: &'w Table,
439 ) {
440 unsafe { <&MainEntity as WorldQuery>::set_table(fetch, &component_id, table) }
442 }
443
444 fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) {
445 <&MainEntity as WorldQuery>::update_component_access(&component_id, access);
446 }
447
448 fn init_state(world: &mut World) -> ComponentId {
449 <&MainEntity as WorldQuery>::init_state(world)
450 }
451
452 fn get_state(components: &Components) -> Option<Self::State> {
453 <&MainEntity as WorldQuery>::get_state(components)
454 }
455
456 fn matches_component_set(
457 &state: &ComponentId,
458 set_contains_id: &impl Fn(ComponentId) -> bool,
459 ) -> bool {
460 <&MainEntity as WorldQuery>::matches_component_set(&state, set_contains_id)
461 }
462 }
463
464 unsafe impl QueryData for MainEntity {
467 const IS_READ_ONLY: bool = true;
468 type ReadOnly = MainEntity;
469 type Item<'w, 's> = Entity;
470
471 fn shrink<'wlong: 'wshort, 'wshort, 's>(
472 item: Self::Item<'wlong, 's>,
473 ) -> Self::Item<'wshort, 's> {
474 item
475 }
476
477 #[inline(always)]
478 unsafe fn fetch<'w, 's>(
479 state: &'s Self::State,
480 fetch: &mut Self::Fetch<'w>,
481 entity: Entity,
482 table_row: TableRow,
483 ) -> Self::Item<'w, 's> {
484 let component =
486 unsafe { <&MainEntity as QueryData>::fetch(state, fetch, entity, table_row) };
487 component.id()
488 }
489 }
490
491 unsafe impl ReadOnlyQueryData for MainEntity {}
493
494 impl ReleaseStateQueryData for MainEntity {
495 fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> {
496 item
497 }
498 }
499}
500
501#[cfg(test)]
502mod tests {
503 use bevy_ecs::{
504 component::Component,
505 entity::Entity,
506 lifecycle::{Add, Remove},
507 observer::On,
508 query::With,
509 system::{Query, ResMut},
510 world::World,
511 };
512
513 use super::{
514 entity_sync_system, EntityRecord, MainEntity, PendingSyncEntity, RenderEntity,
515 SyncToRenderWorld,
516 };
517
518 #[derive(Component)]
519 struct RenderDataComponent;
520
521 #[test]
522 fn sync_world() {
523 let mut main_world = World::new();
524 let mut render_world = World::new();
525 main_world.init_resource::<PendingSyncEntity>();
526
527 main_world.add_observer(
528 |add: On<Add, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
529 pending.push(EntityRecord::Added(add.entity));
530 },
531 );
532 main_world.add_observer(
533 |remove: On<Remove, SyncToRenderWorld>,
534 mut pending: ResMut<PendingSyncEntity>,
535 query: Query<&RenderEntity>| {
536 if let Ok(e) = query.get(remove.entity) {
537 pending.push(EntityRecord::Removed(*e));
538 };
539 },
540 );
541
542 for _ in 0..99 {
544 main_world.spawn_empty();
545 }
546
547 let main_entity = main_world
549 .spawn(RenderDataComponent)
550 .insert(SyncToRenderWorld)
552 .id();
553
554 entity_sync_system(&mut main_world, &mut render_world);
555
556 let mut q = render_world.query_filtered::<Entity, With<MainEntity>>();
557
558 assert!(q.iter(&render_world).count() == 1);
560
561 let render_entity = q.single(&render_world).unwrap();
562 let render_entity_component = main_world.get::<RenderEntity>(main_entity).unwrap();
563
564 assert!(render_entity_component.id() == render_entity);
565
566 let main_entity_component = render_world
567 .get::<MainEntity>(render_entity_component.id())
568 .unwrap();
569
570 assert!(main_entity_component.id() == main_entity);
571
572 main_world.despawn(main_entity);
574
575 entity_sync_system(&mut main_world, &mut render_world);
576
577 assert!(q.iter(&render_world).count() == 0);
579 }
580}