diff --git a/crates/bevy_legion/legion_core/src/borrow.rs b/crates/bevy_legion/legion_core/src/borrow.rs index 7ce11317a2..4e8e5fa4af 100644 --- a/crates/bevy_legion/legion_core/src/borrow.rs +++ b/crates/bevy_legion/legion_core/src/borrow.rs @@ -1,7 +1,6 @@ //! Atomic runtime borrow checking module. //! These types implement something akin to `RefCell`, but are atomically handled allowing them to //! cross thread boundaries. -use std::any::{type_name, Any}; use std::cell::UnsafeCell; use std::hash::{Hash, Hasher}; use std::ops::Deref; @@ -11,13 +10,6 @@ use std::sync::atomic::AtomicIsize; #[cfg(not(debug_assertions))] use std::marker::PhantomData; -pub trait DowncastTypename { - fn downcast_typename_mut(&mut self) -> Option<&mut T>; - fn downcast_typename_ref(&self) -> Option<&T>; - fn is_typename(&self) -> bool; -} - -pub fn type_name_of_val(_val: &T) -> &'static str { type_name::() } /// A `RefCell` implementation which is thread safe. This type performs all the standard runtime /// borrow checking which would be familiar from using `RefCell`. /// @@ -405,6 +397,11 @@ impl<'a, T: 'a> AsRef for RefMut<'a, T> { fn as_ref(&self) -> &T { self.value } } +impl<'a, T: 'a> AsMut for RefMut<'a, T> { + #[inline(always)] + fn as_mut(&mut self) -> &mut T { self.value } +} + impl<'a, T: 'a> std::borrow::Borrow for RefMut<'a, T> { #[inline(always)] fn borrow(&self) -> &T { self.value } @@ -529,6 +526,11 @@ impl<'a, T: 'a> AsRef for RefMapMut<'a, T> { fn as_ref(&self) -> &T { &self.value } } +impl<'a, T: 'a> AsMut for RefMapMut<'a, T> { + #[inline(always)] + fn as_mut(&mut self) -> &mut T { &mut self.value } +} + impl<'a, T: 'a> std::borrow::Borrow for RefMapMut<'a, T> { #[inline(always)] fn borrow(&self) -> &T { &self.value } diff --git a/crates/bevy_legion/legion_core/src/command.rs b/crates/bevy_legion/legion_core/src/command.rs index 2d27da89e4..b0babf3c94 100644 --- a/crates/bevy_legion/legion_core/src/command.rs +++ b/crates/bevy_legion/legion_core/src/command.rs @@ -1,13 +1,13 @@ use crate::{ borrow::AtomicRefCell, cons::{ConsAppend, ConsFlatten}, - entity::{Entity, GuidEntityAllocator}, + entity::Entity, filter::{ChunksetFilterData, Filter}, storage::{Component, ComponentTypeId, Tag, TagTypeId}, world::{ ComponentSource, ComponentTupleSet, IntoComponentSource, PreallocComponentSource, TagLayout, TagSet, World, WorldId, - }, + }, guid_entity_allocator::GuidEntityAllocator, }; use derivative::Derivative; use smallvec::SmallVec; diff --git a/crates/bevy_legion/legion_core/src/downcast_typename.rs b/crates/bevy_legion/legion_core/src/downcast_typename.rs new file mode 100644 index 0000000000..4e0ddf9a2a --- /dev/null +++ b/crates/bevy_legion/legion_core/src/downcast_typename.rs @@ -0,0 +1,9 @@ +use std::any::{type_name, Any}; + +pub trait DowncastTypename { + fn downcast_typename_mut(&mut self) -> Option<&mut T>; + fn downcast_typename_ref(&self) -> Option<&T>; + fn is_typename(&self) -> bool; +} + +pub fn type_name_of_val(_val: &T) -> &'static str { type_name::() } \ No newline at end of file diff --git a/crates/bevy_legion/legion_core/src/entity.rs b/crates/bevy_legion/legion_core/src/entity.rs index 4d066399b4..41e16c1e18 100644 --- a/crates/bevy_legion/legion_core/src/entity.rs +++ b/crates/bevy_legion/legion_core/src/entity.rs @@ -7,7 +7,7 @@ use std::fmt::Display; use std::num::Wrapping; use std::ops::Deref; use std::ops::DerefMut; -use std::{collections::HashSet, sync::Arc}; +use std::sync::Arc; pub type EntityIndex = u32; pub(crate) type EntityVersion = Wrapping; @@ -247,65 +247,6 @@ impl DerefMut for Blocks { fn deref_mut(&mut self) -> &mut Self::Target { self.blocks.deref_mut() } } -#[derive(Default, Debug, Clone)] -pub struct GuidEntityAllocator { - entities: Arc>>, - next_ids: Arc>>, -} - -impl GuidEntityAllocator { - pub fn is_alive(&self, entity: Entity) -> bool { - self.entities.read().contains(&entity) - } - - pub fn push_next_ids(&self, ids: impl Iterator) { - self.next_ids.write().extend(ids); - } - - /// Allocates a new unused `Entity` ID. - pub fn create_entity(&self) -> Entity { - let entity = if !self.next_ids.read().is_empty() { - self.next_ids.write().pop().unwrap() - } else { - Entity::new(rand::random::(), Wrapping(1)) - }; - - self.entities.write().insert(entity); - entity - } - - /// Creates an iterator which allocates new `Entity` IDs. - pub fn create_entities(&self) -> GuidCreateEntityIter { - GuidCreateEntityIter { - allocator: self, - } - } - - pub(crate) fn delete_entity(&self, entity: Entity) -> bool { - self.entities.write().remove(&entity) - } - - pub(crate) fn delete_all_entities(&self) { - self.entities.write().clear(); - } - - pub(crate) fn merge(&self, other: GuidEntityAllocator) { - self.entities.write().extend(other.entities.write().drain()) - } -} - -pub struct GuidCreateEntityIter<'a> { - allocator: &'a GuidEntityAllocator -} - -impl<'a> Iterator for GuidCreateEntityIter<'a> { - type Item = Entity; - - fn next(&mut self) -> Option { - Some(self.allocator.create_entity()) - } -} - /// Manages the allocation and deletion of `Entity` IDs within a world. #[derive(Debug)] pub struct EntityAllocator { diff --git a/crates/bevy_legion/legion_core/src/guid_entity_allocator.rs b/crates/bevy_legion/legion_core/src/guid_entity_allocator.rs new file mode 100644 index 0000000000..a1b6a7c5a2 --- /dev/null +++ b/crates/bevy_legion/legion_core/src/guid_entity_allocator.rs @@ -0,0 +1,54 @@ +use crate::entity::Entity; +use parking_lot::RwLock; +use std::{collections::HashSet, num::Wrapping, sync::Arc}; + +#[derive(Default, Debug, Clone)] +pub struct GuidEntityAllocator { + entities: Arc>>, + next_ids: Arc>>, +} + +impl GuidEntityAllocator { + pub fn is_alive(&self, entity: Entity) -> bool { self.entities.read().contains(&entity) } + + pub fn push_next_ids(&self, ids: impl Iterator) { + self.next_ids.write().extend(ids); + } + + /// Allocates a new unused `Entity` ID. + pub fn create_entity(&self) -> Entity { + let entity = if !self.next_ids.read().is_empty() { + self.next_ids.write().pop().unwrap() + } else { + Entity::new(rand::random::(), Wrapping(1)) + }; + + self.entities.write().insert(entity); + entity + } + + /// Creates an iterator which allocates new `Entity` IDs. + pub fn create_entities(&self) -> GuidCreateEntityIter { + GuidCreateEntityIter { allocator: self } + } + + pub(crate) fn delete_entity(&self, entity: Entity) -> bool { + self.entities.write().remove(&entity) + } + + pub(crate) fn delete_all_entities(&self) { self.entities.write().clear(); } + + pub(crate) fn merge(&self, other: GuidEntityAllocator) { + self.entities.write().extend(other.entities.write().drain()) + } +} + +pub struct GuidCreateEntityIter<'a> { + allocator: &'a GuidEntityAllocator, +} + +impl<'a> Iterator for GuidCreateEntityIter<'a> { + type Item = Entity; + + fn next(&mut self) -> Option { Some(self.allocator.create_entity()) } +} diff --git a/crates/bevy_legion/legion_core/src/lib.rs b/crates/bevy_legion/legion_core/src/lib.rs index 7cb99089fa..70b98aa939 100644 --- a/crates/bevy_legion/legion_core/src/lib.rs +++ b/crates/bevy_legion/legion_core/src/lib.rs @@ -11,6 +11,8 @@ pub mod iterator; pub mod query; pub mod storage; pub mod world; +pub mod downcast_typename; +pub mod guid_entity_allocator; #[cfg(feature = "serialize")] pub mod serialize; diff --git a/crates/bevy_legion/legion_core/src/query.rs b/crates/bevy_legion/legion_core/src/query.rs index 04a4cbdacf..51e48c7218 100644 --- a/crates/bevy_legion/legion_core/src/query.rs +++ b/crates/bevy_legion/legion_core/src/query.rs @@ -940,7 +940,9 @@ where /// /// # Safety /// - /// Incorrectly accessing components that are already borrowed elsewhere is undefined behavior. + /// The normal borrowing restrictions apply for the duration of the iteration: + /// * Components borrowed with `Read` access must not be borrowed mutably elsewhere. + /// * Components borrowed with `Write` access must not be borrowed elsewhere at all. /// /// # Panics /// @@ -996,7 +998,9 @@ where /// /// # Safety /// - /// Incorrectly accessing components that are already borrowed elsewhere is undefined behavior. + /// The normal borrowing restrictions apply for the duration of the iteration: + /// * Components borrowed with `Read` access must not be borrowed mutably elsewhere. + /// * Components borrowed with `Write` access must not be borrowed elsewhere at all. /// /// # Panics /// @@ -1050,7 +1054,9 @@ where /// /// # Safety /// - /// Incorrectly accessing components that are already borrowed elsewhere is undefined behavior. + /// The normal borrowing restrictions apply for the duration of the iteration: + /// * Components borrowed with `Read` access must not be borrowed mutably elsewhere. + /// * Components borrowed with `Write` access must not be borrowed elsewhere at all. /// /// # Panics /// @@ -1104,7 +1110,9 @@ where /// /// # Safety /// - /// Incorrectly accessing components that are already borrowed elsewhere is undefined behavior. + /// The normal borrowing restrictions apply for the duration of the iteration: + /// * Components borrowed with `Read` access must not be borrowed mutably elsewhere. + /// * Components borrowed with `Write` access must not be borrowed elsewhere at all. /// /// # Panics /// @@ -1140,7 +1148,9 @@ where /// /// # Safety /// - /// Incorrectly accessing components that are already borrowed elsewhere is undefined behavior. + /// The normal borrowing restrictions apply for the duration of the iteration: + /// * Components borrowed with `Read` access must not be borrowed mutably elsewhere. + /// * Components borrowed with `Write` access must not be borrowed elsewhere at all. /// /// # Panics /// @@ -1177,7 +1187,9 @@ where /// /// # Safety /// - /// Incorrectly accessing components that are already borrowed elsewhere is undefined behavior. + /// The normal borrowing restrictions apply for the duration of the iteration: + /// * Components borrowed with `Read` access must not be borrowed mutably elsewhere. + /// * Components borrowed with `Write` access must not be borrowed elsewhere at all. /// /// # Panics /// @@ -1246,7 +1258,9 @@ where /// /// # Safety /// - /// Incorrectly accessing components that are already borrowed elsewhere is undefined behavior. + /// The normal borrowing restrictions apply for the duration of the iteration: + /// * Components borrowed with `Read` access must not be borrowed mutably elsewhere. + /// * Components borrowed with `Write` access must not be borrowed elsewhere at all. /// /// # Panics /// @@ -1298,7 +1312,9 @@ where /// /// # Safety /// - /// Incorrectly accessing components that are already borrowed elsewhere is undefined behavior. + /// The normal borrowing restrictions apply for the duration of the iteration: + /// * Components borrowed with `Read` access must not be borrowed mutably elsewhere. + /// * Components borrowed with `Write` access must not be borrowed elsewhere at all. /// /// # Panics /// @@ -1350,7 +1366,9 @@ where /// /// # Safety /// - /// Incorrectly accessing components that are already borrowed elsewhere is undefined behavior. + /// The normal borrowing restrictions apply for the duration of the iteration: + /// * Components borrowed with `Read` access must not be borrowed mutably elsewhere. + /// * Components borrowed with `Write` access must not be borrowed elsewhere at all. /// /// # Panics /// diff --git a/crates/bevy_legion/legion_core/src/serialize/de.rs b/crates/bevy_legion/legion_core/src/serialize/de.rs index 5b914fe683..797cf82ce9 100644 --- a/crates/bevy_legion/legion_core/src/serialize/de.rs +++ b/crates/bevy_legion/legion_core/src/serialize/de.rs @@ -1,11 +1,11 @@ use crate::{ - entity::{Entity, GuidEntityAllocator}, + entity::Entity, index::{ArchetypeIndex, ChunkIndex, SetIndex}, storage::{ ArchetypeData, ArchetypeDescription, Chunkset, ComponentMeta, ComponentTypeId, TagMeta, TagStorage, TagTypeId, }, - world::World, + world::World, guid_entity_allocator::GuidEntityAllocator, }; use serde::{ self, diff --git a/crates/bevy_legion/legion_core/src/storage.rs b/crates/bevy_legion/legion_core/src/storage.rs index 276d6f2a49..b8d81e9c31 100644 --- a/crates/bevy_legion/legion_core/src/storage.rs +++ b/crates/bevy_legion/legion_core/src/storage.rs @@ -697,34 +697,65 @@ impl ArchetypeData { /// Gets the unique ID of this archetype. pub fn id(&self) -> ArchetypeId { self.id } - pub(crate) fn merge(&mut self, mut other: ArchetypeData) { - let other_tags = &other.tags; - for (other_index, mut set) in other.chunk_sets.drain(..).enumerate() { - // search for a matching chunk set - let mut set_match = None; - for self_index in 0..self.chunk_sets.len() { - let mut matches = true; - for &(type_id, ref tags) in self.tags.0.iter() { - unsafe { - let (self_tag_ptr, size, _) = tags.data_raw(); - let (other_tag_ptr, _, _) = other_tags.get(type_id).unwrap().data_raw(); + fn find_chunk_set_by_tags( + &self, + other_tags: &Tags, + other_set_index: SetIndex, + ) -> Option { + // search for a matching chunk set + let mut set_match = None; + for self_set_index in 0..self.chunk_sets.len() { + let self_set_index = SetIndex(self_set_index); + let mut matches = true; + for &(type_id, ref tags) in self.tags.0.iter() { + unsafe { + let (self_tag_ptr, size, _) = tags.data_raw(); + let (other_tag_ptr, _, _) = other_tags.get(type_id).unwrap().data_raw(); - if !tags.element().equals( - self_tag_ptr.as_ptr().add(self_index * size), - other_tag_ptr.as_ptr().add(other_index * size), - ) { - matches = false; - break; - } + if !tags.element().equals( + self_tag_ptr.as_ptr().add(self_set_index.0 * size), + other_tag_ptr.as_ptr().add(other_set_index.0 * size), + ) { + matches = false; + break; } } - - if matches { - set_match = Some(self_index); - break; - } } + if matches { + set_match = Some(self_set_index); + break; + } + } + + set_match + } + + pub(crate) fn find_or_create_chunk_set_by_tags( + &mut self, + src_tags: &Tags, + src_chunk_set_index: SetIndex, + ) -> SetIndex { + let dst_chunk_set_index = self.find_chunk_set_by_tags(src_tags, src_chunk_set_index); + dst_chunk_set_index.unwrap_or_else(|| { + self.alloc_chunk_set(|self_tags| { + for (type_id, other_tags) in src_tags.0.iter() { + unsafe { + let (src, _, _) = other_tags.data_raw(); + let dst = self_tags.get_mut(*type_id).unwrap().alloc_ptr(); + other_tags.element().clone(src.as_ptr(), dst); + } + } + }) + }) + } + + pub(crate) fn move_from(&mut self, mut other: ArchetypeData) { + let other_tags = &other.tags; + for (other_index, mut set) in other.chunk_sets.drain(..).enumerate() { + let other_index = SetIndex(other_index); + let set_match = self.find_chunk_set_by_tags(&other_tags, other_index); + if let Some(chunk_set) = set_match { // if we found a match, move the chunks into the set let target = &mut self.chunk_sets[chunk_set]; @@ -750,6 +781,276 @@ impl ArchetypeData { self.tags.validate(self.chunk_sets.len()); } + /// Given a source world and archetype, step through all of its chunks and copy the data in it + /// into this archetype. The archetype index is provided so that we can produce EntityLocations + /// During this process, we can replace pre-existing entities. This function assumes that any + /// entity referenced in replace_mappings actually exists in the world. The public API in world + /// checks this assumption and panics if it is violated. + /// + /// See also `clone_from_single`, which copies a specific entity + #[allow(clippy::too_many_arguments)] + pub(crate) fn clone_from< + 's, + CloneImplT: crate::world::CloneImpl, + CloneImplResultT: crate::world::CloneImplResult, + EntityReplacePolicyT: crate::world::EntityReplacePolicy<'s>, + >( + &mut self, + src_world: &crate::world::World, + src_archetype: &ArchetypeData, + dst_archetype_index: ArchetypeIndex, + dst_entity_allocator: &crate::guid_entity_allocator::GuidEntityAllocator, + dst_entity_locations: &mut crate::entity::Locations, + clone_impl: &CloneImplT, + clone_impl_result: &mut CloneImplResultT, + entity_replace_policy: &EntityReplacePolicyT, + ) { + // Iterate all the chunk sets within the source archetype + let src_tags = &src_archetype.tags; + for (src_chunk_set_index, src_chunk_set) in src_archetype.chunk_sets.iter().enumerate() { + let src_chunk_set_index = SetIndex(src_chunk_set_index); + let dst_chunk_set_index = + self.find_or_create_chunk_set_by_tags(src_tags, src_chunk_set_index); + + // Iterate all the chunks within the source chunk set + for (_src_chunk_idx, src_chunk) in src_chunk_set.chunks.iter().enumerate() { + // Copy the data from source to destination. Continuously find or create chunks as + // needed until we've copied all the data + let mut entities_remaining = src_chunk.len(); + while entities_remaining > 0 { + // Get or allocate a chunk.. since we could be transforming to a larger component size, it's possible + // that even a brand-new, empty chunk won't be large enough to hold everything in the chunk we are copying from + let dst_free_chunk_index = + self.get_free_chunk(dst_chunk_set_index, entities_remaining); + let dst_chunk_set = &mut self.chunk_sets[dst_chunk_set_index]; + let dst_chunk = &mut dst_chunk_set.chunks[dst_free_chunk_index]; + + // Determine how many entities we will write + let entities_to_write = + std::cmp::min(entities_remaining, dst_chunk.capacity() - dst_chunk.len()); + + // Prepare to write to the chunk storage + let mut writer = dst_chunk.writer(); + let (dst_entities, dst_components) = writer.get(); + + // Find the region of memory we will be reading from in the source chunk + let src_begin_idx = ComponentIndex(src_chunk.len() - entities_remaining); + let src_end_idx = ComponentIndex(src_begin_idx.0 + entities_to_write); + + let dst_begin_idx = ComponentIndex(dst_entities.len()); + let dst_end_idx = ComponentIndex(dst_entities.len() + entities_to_write); + + // Copy all the entities to the destination chunk. The normal case is that we simply allocate + // new entities. + // + // We also allow end-user to specify a HashMap. The key is an Entity from + // the source chunk and the value is an Entity from the destination chunk. Rather than appending + // data to the destination chunk, we will *replace* the data, according to the mapping. This + // is specifically intended for use with hot-reloading data. When some source data is changed, + // we can use the mapping to respawn entities as needed using the new data. + + // We know how many entities will be appended to this list + dst_entities.reserve(dst_entities.len() + entities_to_write); + + for src_entity in &src_chunk.entities[src_begin_idx.0..src_end_idx.0] { + // Determine if there is an entity we will be replacing + let dst_entity = entity_replace_policy.get_dst_entity(*src_entity); + + // The location of the next entity + let location = EntityLocation::new( + dst_archetype_index, + dst_chunk_set_index, + dst_free_chunk_index, + ComponentIndex(dst_entities.len()), + ); + + // Determine the Entity to use for this element + let dst_entity = if let Some(dst_entity) = dst_entity { + // We are replacing data + // Verify that the entity is alive.. this checks the index and version of the entity + // The entity should be alive because World::clone_from verifies this + debug_assert!(dst_entity_allocator.is_alive(dst_entity)); + dst_entity + } else { + // We are appending data, allocate a new entity + dst_entity_allocator.create_entity() + }; + + dst_entity_locations.set(dst_entity, location); + dst_entities.push(dst_entity); + + clone_impl_result.add_result(*src_entity, dst_entity); + } + + ArchetypeData::clone_components( + clone_impl, + src_world, + src_archetype, + src_chunk, + src_begin_idx..src_end_idx, + &dst_entities[dst_begin_idx.0..dst_end_idx.0], + dst_components, + entities_to_write, + ); + + entities_remaining -= entities_to_write; + } + } + } + } + + /// Given a source world, archetype, and entity, copy it into this archetype. The archetype + /// index is provided so that we can produce EntityLocations. + /// During this process, we can replace a pre-existing entity. This function assumes that if + /// replace_mapping is not none, that the entity exists. The public API in world checks this + /// assumption and panics if it is violated. + /// + /// See also `clone_from`, which copies all data + #[allow(clippy::too_many_arguments)] + pub(crate) fn clone_from_single( + &mut self, + src_world: &crate::world::World, + src_archetype: &ArchetypeData, + src_location: &EntityLocation, + dst_archetype_index: ArchetypeIndex, + dst_entity_allocator: &crate::guid_entity_allocator::GuidEntityAllocator, + dst_entity_locations: &mut crate::entity::Locations, + clone_impl: &C, + replace_mapping: Option, + ) -> Entity { + // We are reading from a specific chunk set within the source archetype + let src_tags = &src_archetype.tags; + let src_chunk_set_index = src_location.set(); + let src_chunk_set = &src_archetype.chunk_sets[src_chunk_set_index]; + + // Find or create the chunk set that matches the source chunk set + let dst_chunk_set_index = + self.find_or_create_chunk_set_by_tags(src_tags, src_chunk_set_index); + + // Get the source chunk + let src_chunk_idx = src_location.chunk(); + let src_chunk = &src_chunk_set.chunks[src_chunk_idx]; + + // Get or allocate a chunk.. since we could be transforming to a larger component size, it's possible + // that even a brand-new, empty chunk won't be large enough to hold everything in the chunk we are copying from + let dst_free_chunk_index = self.get_free_chunk(dst_chunk_set_index, 1); + let dst_chunk_set = &mut self.chunk_sets[dst_chunk_set_index]; + let dst_chunk = &mut dst_chunk_set.chunks[dst_free_chunk_index]; + + // Determine how many entities we will write + let entities_to_write = 1; + + // Prepare to write to the chunk storage + let mut writer = dst_chunk.writer(); + let (dst_entities, dst_components) = writer.get(); + + // Find the region of memory we will be reading from in the source chunk + let src_begin_idx = src_location.component(); + let src_end_idx = ComponentIndex(src_begin_idx.0 + 1); + + // We know how many entities will be appended to this list + let dst_begin_idx = ComponentIndex(dst_entities.len()); + let dst_end_idx = ComponentIndex(dst_entities.len() + entities_to_write); + + // Copy the entity to the destination chunk. The normal case is that we simply allocate + // a new entity. + // + // We also allow end-user to specify a Option. The src Entity from will *replace* the + // data of the given Entity + + // The location of the next entity + let location = EntityLocation::new( + dst_archetype_index, + dst_chunk_set_index, + dst_free_chunk_index, + ComponentIndex(dst_entities.len()), + ); + + let dst_entity = if let Some(dst_entity) = replace_mapping { + // We are replacing data + // Verify that the entity is alive.. this checks the index and version of the entity + // The entity should be alive because World::clone_from verifies this + debug_assert!(dst_entity_allocator.is_alive(dst_entity)); + dst_entity + } else { + // We are appending data, allocate a new entity + dst_entity_allocator.create_entity() + }; + + dst_entity_locations.set(dst_entity, location); + dst_entities.push(dst_entity); + + ArchetypeData::clone_components( + clone_impl, + src_world, + src_archetype, + src_chunk, + src_begin_idx..src_end_idx, + &dst_entities[dst_begin_idx.0..dst_end_idx.0], + dst_components, + entities_to_write, + ); + + dst_entity + } + + /// Implements shared logic between `clone_from` and `clone_from_single`. For every component type, + /// in the given archetype, + #[allow(clippy::too_many_arguments)] + fn clone_components( + clone_impl: &C, + src_world: &crate::world::World, + src_archetype: &ArchetypeData, + src_chunk: &ComponentStorage, + src_range: core::ops::Range, + dst_entities: &[Entity], + dst_components: &UnsafeCell, + entities_to_write: usize, + ) { + for (src_type, _) in src_archetype.description().components() { + let dst_components = unsafe { &mut *dst_components.get() }; + + // Look up what type we should transform the data into (can be the same type, meaning it should be cloned) + let (dst_type, _) = clone_impl.map_component_type(*src_type); + + // Create a writer that will insert the data into the destination chunk + let mut dst_component_writer = dst_components + .get_mut(dst_type) + .expect("ComponentResourceSet missing in clone_from") + .writer(); + + // Find the data in the source chunk + let src_component_storage = src_chunk + .components(*src_type) + .expect("ComponentResourceSet missing in clone_from"); + + // Now copy the data + unsafe { + let (src_component_chunk_data, src_element_size, _) = + src_component_storage.data_raw(); + + // offset to the first entity we want to copy from the source chunk + let src_data = src_component_chunk_data.add(src_element_size * src_range.start.0); + + // allocate the space we need in the destination chunk + let dst_data = dst_component_writer.reserve_raw(entities_to_write).as_ptr(); + + // Delegate the clone operation to the provided CloneImpl + clone_impl.clone_components( + src_world, + src_chunk, + src_range.clone(), + *src_type, + &src_chunk.entities[src_range.start.0..src_range.end.0], + dst_entities, + src_data, + dst_data, + entities_to_write, + ); + } + } + } + /// Iterate all entities in existence by iterating across archetypes, chunk sets, and chunks pub(crate) fn iter_entities<'a>(&'a self) -> impl Iterator + 'a { self.chunk_sets.iter().flat_map(move |set| { diff --git a/crates/bevy_legion/legion_core/src/world.rs b/crates/bevy_legion/legion_core/src/world.rs index c215709146..0a2a2ab495 100644 --- a/crates/bevy_legion/legion_core/src/world.rs +++ b/crates/bevy_legion/legion_core/src/world.rs @@ -2,8 +2,9 @@ use crate::borrow::Ref; use crate::borrow::RefMut; use crate::entity::BlockAllocator; use crate::entity::Entity; +use crate::guid_entity_allocator::GuidEntityAllocator; use crate::entity::EntityLocation; -use crate::entity::{GuidEntityAllocator, Locations}; +use crate::entity::Locations; use crate::event::Event; use crate::filter::ArchetypeFilterData; use crate::filter::ChunksetFilterData; @@ -27,6 +28,7 @@ use crate::storage::Tags; use crate::tuple::TupleEq; use parking_lot::Mutex; use std::cell::UnsafeCell; +use std::collections::HashMap; use std::iter::Enumerate; use std::iter::Fuse; use std::iter::FusedIterator; @@ -292,18 +294,9 @@ impl World { } if self.entity_allocator.delete_entity(entity) { - // find entity's chunk let location = self.entity_locations.get(entity).unwrap(); - let chunk = self.storage_mut().chunk_mut(location).unwrap(); - - // swap remove with last entity in chunk - if let Some(swapped) = chunk.swap_remove(location.component(), true) { - // record swapped entity's new location - self.entity_locations.set(swapped, location); - } - + self.delete_location(location); trace!(world = self.id().0, ?entity, "Deleted entity"); - true } else { false @@ -319,6 +312,17 @@ impl World { self.entity_allocator.delete_all_entities(); } + fn delete_location(&mut self, location: EntityLocation) { + // find entity's chunk + let chunk = self.storage_mut().chunk_mut(location).unwrap(); + + // swap remove with last entity in chunk + if let Some(swapped) = chunk.swap_remove(location.component(), true) { + // record swapped entity's new location + self.entity_locations.set(swapped, location); + } + } + fn find_chunk_with_delta( &mut self, source_location: EntityLocation, @@ -728,6 +732,33 @@ impl World { /// Determines if the given `Entity` is alive within this `World`. pub fn is_alive(&self, entity: Entity) -> bool { self.entity_allocator.is_alive(entity) } + /// Returns the entity's component types, if the entity exists. + pub fn entity_component_types( + &self, + entity: Entity, + ) -> Option<&[(ComponentTypeId, ComponentMeta)]> { + if !self.is_alive(entity) { + return None; + } + let location = self.entity_locations.get(entity); + let archetype = location + .map(|location| self.storage().archetype(location.archetype())) + .unwrap_or(None); + archetype.map(|archetype| archetype.description().components()) + } + + /// Returns the entity's tag types, if the entity exists. + pub fn entity_tag_types(&self, entity: Entity) -> Option<&[(TagTypeId, TagMeta)]> { + if !self.is_alive(entity) { + return None; + } + let location = self.entity_locations.get(entity); + let archetype = location + .map(|location| self.storage().archetype(location.archetype())) + .unwrap_or(None); + archetype.map(|archetype| archetype.description().tags()) + } + /// Iteratively defragments the world's internal memory. /// /// This compacts entities into fewer more continuous chunks. @@ -765,9 +796,9 @@ impl World { } } - /// Merge this world with another, copying all appropriate archetypes, tags entities and components - /// into this world. - pub fn merge(&mut self, world: World) { + /// Move entities from a world to this world, copying all appropriate archetypes, + /// tags entities and components into this world. + pub fn move_from(&mut self, world: World) { let span = span!(Level::INFO, "Merging worlds", source = world.id().0, destination = ?self.id()); let _guard = span.enter(); @@ -793,7 +824,7 @@ impl World { self.storage_mut() .archetype_mut(arch_index) .unwrap() - .merge(archetype); + .move_from(archetype); arch_index } else { // archetype does not already exist, append @@ -810,6 +841,199 @@ impl World { } } + /// This will *copy* the data from `src_world` into this world. The logic to do the copy is + /// delegated to the `clone_impl` provided by the user. In addition to simple copying, it's also + /// possible to transform from one type to another. This is useful for cases where you want to + /// read from serializable data (like a physics shape definition) and construct something that + /// isn't serializable (like a handle to a physics body) + /// + /// By default, all entities in the new world will be assigned a new Entity. `result_mappings` + /// (if not None) will be populated with the old/new Entities, which allows for mapping data + /// between the old and new world. + /// + /// If you want to replace existing entities (for example to hot-reload data from a file,) + /// populate `replace_mappings`. For every entry in this map, the key must exist in the source + /// world and the value must exist in the destination world. All entities in the destination + /// world referenced by this map will be deleted, and the entities copied over will be assigned + /// the same entity. If these constraints are not met, this function will panic. + pub fn clone_from< + 's, + CloneImplT: CloneImpl, + CloneImplResultT: CloneImplResult, + EntityReplacePolicyT: EntityReplacePolicy<'s>, + >( + &mut self, + src_world: &World, + clone_impl: &CloneImplT, + clone_impl_result: &mut CloneImplResultT, + entity_replace_policy: &'s EntityReplacePolicyT, + ) { + let span = span!(Level::INFO, "CloneMerging worlds", source = src_world.id().0, destination = ?self.id()); + let _guard = span.enter(); + + let src_storage = unsafe { &(*src_world.storage.get()) }; + let dst_storage = unsafe { &mut (*self.storage.get()) }; + + // First check that all the src entities exist in the source world. We're assuming the + // source data will be available later to replace the data we're about to delete + for k in entity_replace_policy.src_entities() { + if !src_world.entity_allocator.is_alive(k) { + panic!("clone_from assumes all replace_mapping keys exist in the source world"); + } + } + + // Delete all the data associated with dst_entities. This leaves the + // associated entities in a dangling state, but we'll fix this later when we copy the + // data over + for entity_to_replace in entity_replace_policy.dst_entities() { + if self.entity_allocator.is_alive(entity_to_replace) { + let location = self + .entity_locations + .get(entity_to_replace) + .expect("Failed to get location of live entity"); + self.delete_location(location); + } else { + panic!( + "clone_from assumes all replace_mapping values exist in the destination world" + ); + } + } + + // Iterate all archetypes in the src world + for src_archetype in src_storage.archetypes() { + let archetype_data = ArchetypeFilterData { + component_types: self.storage().component_types(), + tag_types: self.storage().tag_types(), + }; + + let dst_archetype_index = World::find_or_create_archetype_for_clone_move( + clone_impl, + src_archetype.description(), + archetype_data, + dst_storage, + ); + + // Do the clone_from for this archetype + dst_storage + .archetype_mut(dst_archetype_index) + .unwrap() + .clone_from( + &src_world, + src_archetype, + dst_archetype_index, + &self.entity_allocator, + &mut self.entity_locations, + clone_impl, + clone_impl_result, + entity_replace_policy, + ); + } + } + + /// This will *copy* the `src_entity` from `src_world` into this world. The logic to do the copy + /// is delegated to the `clone_impl` provided by the user. In addition to simple copying, it's + /// also possible to transform from one type to another. This is useful for cases where you want + /// to read from serializable data (like a physics shape definition) and construct something + /// that isn't serializable (like a handle to a physics body) + /// + /// By default, the entity in the new world will be assigned a new Entity. The return value + /// indicates the Entity in the new world, which allows for mapping data the old and new world. + /// + /// If you want to replace an existing entity (for example to hot-reload data from a file,) + /// populate `replace_mapping`. This entity must exist in the destination world. The entity in + /// the destination world will be deleted, and the entity copied over will be assigned + /// the same entity. If these constraints are not met, this function will panic. + pub fn clone_from_single( + &mut self, + src_world: &World, + src_entity: Entity, + clone_impl: &C, + replace_mapping: Option, + ) -> Entity { + let span = span!(Level::INFO, "CloneMergingSingle worlds", source = src_world.id().0, destination = ?self.id()); + let _guard = span.enter(); + + let src_storage = unsafe { &(*src_world.storage.get()) }; + let dst_storage = unsafe { &mut (*self.storage.get()) }; + + if !src_world.entity_allocator.is_alive(src_entity) { + panic!("src_entity not alive"); + } + + // Erase all entities that are referred to by value. The code following will update the location + // of all these entities to point to new, valid locations + if let Some(replace_mapping) = replace_mapping { + if self.entity_allocator.is_alive(replace_mapping) { + let location = self + .entity_locations + .get(replace_mapping) + .expect("Failed to get location of live entity"); + self.delete_location(location); + } else { + panic!("clone_from_single assumes entity_mapping exists in the destination world"); + } + } + + let src_location = src_world.entity_locations.get(src_entity).unwrap(); + let src_archetype = &src_storage.archetypes()[src_location.archetype()]; + + // Iterate all archetypes in the src world + let archetype_data = ArchetypeFilterData { + component_types: self.storage().component_types(), + tag_types: self.storage().tag_types(), + }; + + let dst_archetype_index = World::find_or_create_archetype_for_clone_move( + clone_impl, + src_archetype.description(), + archetype_data, + dst_storage, + ); + + // Do the clone_from for this archetype + dst_storage + .archetype_mut(dst_archetype_index) + .unwrap() + .clone_from_single( + &src_world, + src_archetype, + &src_location, + dst_archetype_index, + &self.entity_allocator, + &mut self.entity_locations, + clone_impl, + replace_mapping, + ) + } + + fn find_or_create_archetype_for_clone_move( + clone_impl: &C, + src_archetype_description: &ArchetypeDescription, + archetype_data: ArchetypeFilterData, + dst_storage: &mut Storage, + ) -> ArchetypeIndex { + // Build the archetype that we will write into. The caller of this function provides an + // impl to do the clone, optionally transforming components from one type to another + let mut dst_archetype = ArchetypeDescription::default(); + for (from_type_id, _from_meta) in src_archetype_description.components() { + let (into_type_id, into_meta) = clone_impl.map_component_type(*from_type_id); + dst_archetype.register_component_raw(into_type_id, into_meta); + } + + // Find or create the archetype in the destination world + let matches = dst_archetype + .matches(archetype_data) + .matching_indices() + .next(); + + // If it doesn't exist, allocate it + if let Some(arch_index) = matches { + ArchetypeIndex(arch_index) + } else { + dst_storage.alloc_archetype(dst_archetype).0 + } + } + fn find_archetype(&self, tags: &mut T, components: &mut C) -> Option where T: for<'a> Filter>, @@ -900,6 +1124,155 @@ impl Default for World { fn default() -> Self { Self::new() } } +/// Describes how to handle a `clone_from`. Allows the user to transform components from one type +/// to another and provide their own implementation for cloning/transforming +pub trait CloneImpl { + /// When a component of the provided `component_type` is encountered, we will transfer data + /// from it into the returned component type. For a basic clone implementation, this function + /// should return the same type as was passed into it + fn map_component_type( + &self, + component_type_id: ComponentTypeId, + ) -> (ComponentTypeId, ComponentMeta); + + /// When called, the implementation should copy the data from src_data to dst_data. The + /// src_world and src_entities are provided so that other components on the same Entity can + /// be looked up. The dst_resources are provided so that any required side effects to resources + /// (like registering a physics body into a physics engine) can be implemented. + #[allow(clippy::too_many_arguments)] + fn clone_components( + &self, + src_world: &World, + src_component_storage: &ComponentStorage, + src_component_storage_indexes: core::ops::Range, + src_type: ComponentTypeId, + src_entities: &[Entity], + dst_entities: &[Entity], + src_data: *const u8, + dst_data: *mut u8, + num_components: usize, + ); +} + +/// Used along with `CloneImpl`, allows receiving results from a `clone_from` or `clone_from_single` +/// call. +pub trait CloneImplResult { + /// For every entity that is copied, this function will be called, passing the entity in the + /// source and destination worlds + fn add_result(&mut self, src_entity: Entity, dst_entity: Entity); +} + +/// Used along with `CloneImpl`, allows specifying that certain entities in the receiving world should +/// be replaced with entities from the source world. +/// +/// A typical implementation of this trait would be to wrap a HashMap. `src_entities` would be +/// implemented by returning keys(), `dst_entities` would be implemented by returning values(), and +/// `get_dst_entity` would be implemented by returning the result of get(src_entity). +/// +/// Default implementations provided in legion include: +/// * `NoneEntityReplacePolicy` - No entity replacement will occur +/// * `HashMapCloneImplResult` - Wraps the standard library's HashMap. +pub trait EntityReplacePolicy<'s> { + /// Returns all entities in the source world that will replace data in the destination world + /// + /// # Safety + /// + /// * All entities returned via the iterator must exist in the source world + /// * All entities that will be copied from the source world must be included in the + /// returned iterator. + fn src_entities<'a>(&'s self) -> Box + 'a> + where + 's: 'a; + + /// Returns all entities in the destination world that will be replaced + /// + /// # Safety + /// + /// * All entities returned via the iterator must exist in the destination world + /// * All entities that will be replaced in the destination world must be included in the + /// returned iterator + fn dst_entities<'a>(&'s self) -> Box + 'a> + where + 's: 'a; + + /// Returns the entity in the destination world that will be replaced by the given entity in the + /// source world, otherwise None if the entity in the source world should not replace anything. + /// + /// # Safety + /// + /// * All entities passed into this function that result in a non-None return value must be + /// included in the iterator returned by `src_entities` + /// * All entities returned by this function must be included in the iterator returned by + /// `dst_entities` + fn get_dst_entity(&self, src_entity: Entity) -> Option; +} + +/// Used to opt-out of receiving results from a `clone_from` or `clone_from_single` call +/// (See comments on `CloneImplResult`) +pub struct NoneCloneImplResult; +impl CloneImplResult for NoneCloneImplResult { + fn add_result(&mut self, _src_entity: Entity, _dst_entity: Entity) { + // do nothing + } +} + +/// Used to opt-out of replacing entities during a `clone_from` or `clone_from_single` call. +/// (See comments on `EntityReplacePolicy`) +pub struct NoneEntityReplacePolicy; +impl<'s> EntityReplacePolicy<'s> for NoneEntityReplacePolicy { + fn src_entities<'a>(&self) -> Box + 'a> + where + 's: 'a, + { + Box::new(std::iter::Empty::default()) + } + + fn dst_entities<'a>(&self) -> Box + 'a> + where + 's: 'a, + { + Box::new(std::iter::Empty::default()) + } + + fn get_dst_entity(&self, _src_entity: Entity) -> Option { None } +} + +/// Default implementation of `CloneImplResult` that uses a hash map. Keys are entities in the +/// source world and values are entities in the destination world. (See comments on +/// `CloneImplResult`) +pub struct HashMapCloneImplResult<'m>(pub &'m mut HashMap); + +impl<'m> CloneImplResult for HashMapCloneImplResult<'m> { + fn add_result(&mut self, src_entity: Entity, dst_entity: Entity) { + self.0.insert(src_entity, dst_entity); + } +} + +/// Default implementation of `EntityReplacePolicy` that uses a hash map. Keys are entities in the +/// source world and values are entities in the destination world. (See comments on +/// `EntityReplacePolicy`) +pub struct HashMapEntityReplacePolicy<'m>(pub &'m HashMap); + +impl<'m, 's> EntityReplacePolicy<'s> for HashMapEntityReplacePolicy<'m> { + fn src_entities<'a>(&'s self) -> Box + 'a> + where + 's: 'a, + { + Box::new(self.0.keys().cloned()) + } + + fn dst_entities<'a>(&'s self) -> Box + 'a> + where + 's: 'a, + { + Box::new(self.0.values().cloned()) + } + + fn get_dst_entity(&self, src_entity: Entity) -> Option { + self.0.get(&src_entity).copied() + } +} + #[derive(Error, Debug)] pub enum EntityMutationError { #[error("entity does not exist")] @@ -1874,7 +2247,7 @@ mod tests { } #[test] - fn merge() { + fn move_from() { let universe = Universe::new(); let mut a = universe.create_world(); let mut b = universe.create_world(); @@ -1895,7 +2268,7 @@ mod tests { ], )[0]; - b.merge(a); + b.move_from(a); assert_eq!(*b.get_component::(entity_b).unwrap(), Pos(7., 8., 9.)); assert_eq!(*b.get_component::(entity_a).unwrap(), Pos(1., 2., 3.)); diff --git a/crates/bevy_legion/legion_systems/src/resource.rs b/crates/bevy_legion/legion_systems/src/resource.rs index e044b821b4..ec9822415e 100644 --- a/crates/bevy_legion/legion_systems/src/resource.rs +++ b/crates/bevy_legion/legion_systems/src/resource.rs @@ -1,6 +1,6 @@ use downcast_rs::{impl_downcast, Downcast}; use fxhash::FxHashMap; -use legion_core::borrow::DowncastTypename; +use legion_core::downcast_typename::DowncastTypename; use legion_core::borrow::{AtomicRefCell, Ref, RefMut}; use legion_core::query::{Read, ReadOnly, Write}; use std::{ diff --git a/crates/bevy_legion/src/lib.rs b/crates/bevy_legion/src/lib.rs index 1acc5c11f0..f10f71358e 100644 --- a/crates/bevy_legion/src/lib.rs +++ b/crates/bevy_legion/src/lib.rs @@ -179,10 +179,10 @@ //! let mut world_a = universe.create_world(); //! let mut world_b = universe.create_world(); //! -//! // Merge all entities from `world_b` into `world_a` +//! // Move all entities from `world_b` into `world_a` //! // Entity IDs are guarenteed to be unique across worlds and will //! // remain unchanged across the merge. -//! world_a.merge(world_b); +//! world_a.move_from(world_b); //! ``` //! //! ### Chunk Iteration diff --git a/crates/bevy_scene/src/serde/world.rs b/crates/bevy_scene/src/serde/world.rs index 4d0b48048e..a8af2d5be7 100644 --- a/crates/bevy_scene/src/serde/world.rs +++ b/crates/bevy_scene/src/serde/world.rs @@ -2,7 +2,8 @@ use crate::ComponentRegistry; use legion::{ - entity::{EntityIndex, GuidEntityAllocator}, + entity::EntityIndex, + guid_entity_allocator::GuidEntityAllocator, prelude::*, storage::{ ArchetypeDescription, ComponentMeta, ComponentResourceSet, ComponentTypeId, TagMeta, diff --git a/examples/scene/load_scene.rs b/examples/scene/load_scene.rs index 7bd4be7207..eb437c9ae2 100644 --- a/examples/scene/load_scene.rs +++ b/examples/scene/load_scene.rs @@ -7,6 +7,7 @@ fn main() { // Registering components informs Bevy that they exist. This allows them to be used when loading/saving scenes .register_component::() .register_component::() + // .add_startup_system(setup) .add_startup_system(load_scene) .run(); }