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