diff --git a/src/archetype.rs b/src/archetype.rs index 09ec6b78..7735e216 100644 --- a/src/archetype.rs +++ b/src/archetype.rs @@ -8,7 +8,8 @@ use crate::alloc::alloc::{alloc, dealloc, Layout}; use crate::alloc::boxed::Box; use crate::alloc::{vec, vec::Vec}; -use core::any::{type_name, TypeId}; +use crate::cloning::TypeUnknownToCloner; +use core::any::{type_name, Any, TypeId}; use core::fmt; use core::hash::{BuildHasher, BuildHasherDefault, Hasher}; use core::ops::{Deref, DerefMut}; @@ -420,6 +421,78 @@ impl Archetype { pub fn ids(&self) -> &[u32] { &self.entities[0..self.len as usize] } + + pub(crate) fn try_clone( + &mut self, + cloner: &crate::cloning::Cloner, + ) -> Result { + // Make sure all sized component types are known, so we don't leak memory if we fail halfway + if let Some(unsupported_type) = self + .types + .iter() + .filter(|info| info.layout.size() != 0) + .find(|info| cloner.typeid_to_clone_fn.get(&info.id).is_none()) + { + return Err(TypeUnknownToCloner { + #[cfg(debug_assertions)] + type_name: unsupported_type.type_name, + type_id: unsupported_type.type_id(), + }); + } + + // Allocate the new data block with the same number of entities & same component types + let new_data: Box<[Data]> = self + .types + .iter() + .zip(&*self.data) + .map(|(info, old)| { + let storage: NonNull = if info.layout.size() == 0 { + // This is a zero-sized type, so no need to allocate or copy any data + NonNull::new(info.layout.align() as *mut u8).unwrap() + } else { + // Allocate memory for the cloned data + let layout = Layout::from_size_align( + info.layout.size() * self.capacity() as usize, + info.layout.align(), + ) + .unwrap(); + let new_storage = { + let mem = unsafe { alloc(layout) }; + NonNull::new(mem) + .unwrap_or_else(|| alloc::alloc::handle_alloc_error(layout)) + }; + + // Clone the data for all instances of the component + let clone_fn = cloner.typeid_to_clone_fn.get(&info.id).expect( + "all types should be supported by cloner (because we checked earlier)", + ); + unsafe { + clone_fn.call( + old.storage.as_ptr(), + new_storage.as_ptr(), + self.len as usize, + ) + }; + + new_storage + }; + Ok(Data { + state: AtomicBorrow::new(), // &mut self guarantees no outstanding borrows + storage, + }) + }) + .collect::>()?; + + // Clone the other fields of the archetype and return the new instance + Ok(Self { + types: self.types.clone(), + type_ids: self.type_ids.clone(), + index: self.index.clone(), + len: self.len, + entities: self.entities.clone(), + data: new_data, + }) + } } impl Drop for Archetype { @@ -493,6 +566,7 @@ impl Hasher for TypeIdHasher { /// faster no-op hash. pub(crate) type TypeIdMap = HashMap>; +#[derive(Clone)] struct OrderedTypeIdMap(Box<[(TypeId, V)]>); impl OrderedTypeIdMap { diff --git a/src/bundle.rs b/src/bundle.rs index 842a3b50..60ecb4f4 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -113,7 +113,7 @@ pub struct DynamicClone { } impl DynamicClone { - /// Create a new type ereased cloner for the type T + /// Create a new type erased cloner for the type T pub fn new() -> Self { Self { func: |src, f| { diff --git a/src/cloning.rs b/src/cloning.rs new file mode 100644 index 00000000..ed639de9 --- /dev/null +++ b/src/cloning.rs @@ -0,0 +1,297 @@ +//! This module provides a mechanism to efficiently clone a given [World](crate::world::World). +//! +//! As each component for each entity in a World is stored in a type-erased fashion, cloning entity +//! components requires registering each component with a [Cloner]. +//! +//! See the documentation for [World::try_clone()](crate::world::World::try_clone()) for example usage. + +use crate::archetype::TypeIdMap; +use core::any::TypeId; +use core::ptr; + +/// A type erased way to copy or clone multiple instances of a type. +pub(crate) struct BulkCloneFunction(unsafe fn(*const u8, *mut u8, usize)); + +impl BulkCloneFunction { + pub(crate) unsafe fn call(&self, src: *const u8, dst: *mut u8, count: usize) { + (self.0)(src, dst, count) + } +} + +/// Maps component types which can be cloned or copied to their relevant cloning +/// or copying function. +/// +/// Populating such an object with all component types present in the world via +/// [`add_copyable`](Self::add_copyable) or +/// [`add_clonable`](Self::add_cloneable) +/// is required to +/// use [`World::try_clone`][crate::World::try_clone]. +/// +/// A [Cloner] instance is safe to reuse. +/// +/// Registering types which are unused is allowed. +#[derive(Default)] +pub struct Cloner { + pub(crate) typeid_to_clone_fn: TypeIdMap, +} + +impl Cloner { + /// Creates a new [Cloner]. + /// + /// The cloner is not aware of any types out of the box: all types present + /// in the [World](crate::world::World) as components (even built in types + /// such as [i32](core::i32)) must be added using + /// [`add_copyable`](Self::add_copyable) or + /// [`add_clonable`](Self::add_cloneable) before using this Cloner. + pub fn new() -> Self { + Self::default() + } +} + +impl Cloner { + /// Adds a component type which is copyable. + pub fn add_copyable(&mut self) + where + C: Copy + 'static, + { + unsafe fn clone(src: *const u8, dst: *mut u8, count: usize) + where + C: Copy, + { + let src = src.cast::(); + let dst = dst.cast::(); + + ptr::copy_nonoverlapping(src, dst, count); + } + + self.typeid_to_clone_fn + .insert(TypeId::of::(), BulkCloneFunction(clone::)); + } + + /// Adds a component type which is cloneable. + /// + /// If `C` is actually copyable, using [`add_copyable`][Self::add_copyable] + /// is more efficient. + pub fn add_cloneable(&mut self) + where + C: Clone + 'static, + { + unsafe fn clone(src: *const u8, dst: *mut u8, count: usize) + where + C: Clone, + { + let src = src.cast::(); + let dst = dst.cast::(); + + for idx in 0..count { + let val = (*src.add(idx)).clone(); + dst.add(idx).write(val); + } + } + + self.typeid_to_clone_fn + .insert(TypeId::of::(), BulkCloneFunction(clone::)); + } +} + +/// Error returned when the [`Cloner`] has not had [`Cloner::add_cloneable`] or +/// [`Cloner::add_copyable`] called for the contained type. +/// +/// When compiled with debug assertions enabled, this error will include the +/// name of the missing type in its [Display](std::fmt::Display) output. +#[derive(Debug)] +pub struct TypeUnknownToCloner { + /// The name of the type which was unrecognized. + /// + /// This is subject to the same guarantees and caveats as [`core::any::type_name()`]. + #[cfg(debug_assertions)] + pub type_name: &'static str, + + /// The id of the type which was unrecognized. + /// + /// This is subject to the same guarantees and caveats as [`core::any::TypeId::of()`]. + pub type_id: TypeId, +} + +#[cfg(feature = "std")] +impl std::fmt::Display for TypeUnknownToCloner { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + #[cfg(debug_assertions)] + let type_name = self.type_name; + #[cfg(not(debug_assertions))] + let type_name = ""; + write!( + f, + "Type unknown to Cloner: {type_name} (TypeId: {:?})", + self.type_id + ) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TypeUnknownToCloner {} + +#[cfg(test)] +mod tests { + use alloc::{borrow::ToOwned, string::String}; + + use super::*; + use crate::*; + + #[derive(PartialEq, Debug, Copy, Clone)] + struct Position([f32; 2]); + + #[derive(PartialEq, Debug, Clone)] + struct CallSign(String); + + #[derive(Copy, Clone)] + struct AZeroSizedType; + + #[test] + fn cloning_works_with_basic_types() { + // copyable types + let int0 = 0; + let int1 = 1; + let p0 = Position([0.0, 0.0]); + let p1 = Position([1.0, 1.0]); + // cloneable types + let str0 = "Ada".to_owned(); + let str1 = "Bob".to_owned(); + let n0 = CallSign("Zebra".into()); + let n1 = CallSign("Yankee".into()); + + let mut world0 = World::new(); + let entity0 = world0.spawn((int0, p0, str0, n0)); + let entity1 = world0.spawn((int1, p1, str1, n1)); + + let mut cloner = Cloner::new(); + cloner.add_copyable::(); + cloner.add_copyable::(); + cloner.add_cloneable::(); + cloner.add_cloneable::(); + + let world1 = world0.try_clone(&cloner).expect("clone should succeed"); + + assert_eq!( + world0.len(), + world1.len(), + "cloned world should have same entity count as original world" + ); + + type AllComponentsQuery = ( + &'static i32, + &'static Position, + &'static String, + &'static CallSign, + ); + + for entity in [entity0, entity1] { + let w0_e = world0.entity(entity).expect("w0 entity should exist"); + let w1_e = world1.entity(entity).expect("w1 entity should exist"); + assert!(w0_e.satisfies::()); + assert!(w1_e.satisfies::()); + + assert_eq!( + w0_e.query::().get().unwrap(), + w1_e.query::().get().unwrap() + ); + } + } + + #[test] + fn cloning_works_with_zero_sized_types() { + let mut world0 = World::new(); + let entity_zst_only = world0.spawn((AZeroSizedType,)); + let entity_mixed = world0.spawn(("John".to_owned(), AZeroSizedType)); + + let mut cloner = Cloner::new(); + // (Zero sized type does not need to be registered, as it never needs to actually be cloned) + cloner.add_cloneable::(); + + let world1 = world0.try_clone(&cloner).expect("clone should succeed"); + + assert!(world1 + .entity(entity_zst_only) + .expect("entity should exist in cloned world") + .has::()); + assert!(world1 + .entity(entity_mixed) + .expect("entity should exist in cloned world") + .satisfies::<(&String, &AZeroSizedType)>()); + } + + #[test] + fn cloning_gives_identical_entity_ids() { + // This test ensures that a cloned world's spawned entity ids do not diverge from entity ids + // created by the original world - i.e. that cloning does not break determinism. + + let mut world0 = World::new(); + let p0 = Position([1.0, 1.0]); + + // add & remove an entity to catch errors related to entities being given different ids + let e0 = world0.spawn((p0.clone(),)); + let _e1 = world0.spawn((p0,)); + world0.despawn(e0).expect("despawn should succeed"); + + let mut cloner = Cloner::new(); + cloner.add_cloneable::(); + cloner.add_copyable::(); + + let mut world1 = world0.try_clone(&cloner).expect("clone should succeed"); + + let world0_e2 = world0.spawn((p0,)); + let world1_e2 = world1.spawn((p0,)); + assert_eq!( + world0_e2, world1_e2, + "entity id for two worlds should be equal for newly spawned entity" + ); + } + + #[test] + fn cloner_having_unused_types_registered_is_okay() { + let mut world0 = World::new(); + world0.spawn((1,)); + + let mut cloner = Cloner::new(); + cloner.add_copyable::(); + cloner.add_cloneable::(); // unused type + + let world1 = world0.try_clone(&cloner).unwrap(); + assert_eq!(world0.len(), world1.len()); + } + + #[test] + fn cloner_can_be_reused() { + let mut world0 = World::new(); + world0.spawn((1,)); + + let mut cloner = Cloner::new(); + cloner.add_copyable::(); + + let world1 = world0.try_clone(&cloner).unwrap(); + let mut world2 = world0.try_clone(&cloner).unwrap(); + let world3 = world2.try_clone(&cloner).unwrap(); + + for cloned in [world1, world2, world3] { + assert_eq!(world0.len(), cloned.len()); + } + } + + #[test] + fn unknown_type_is_reported() { + let mut world0 = World::new(); + world0.spawn((Position([1.0, 1.0]),)); + + let cloner = Cloner::new(); + + match world0.try_clone(&cloner) { + Ok(_) => { + panic!("cloning should have failed because Position was not registered with Cloner") + } + Err(err) => { + #[cfg(debug_assertions)] + assert!(err.type_name.contains("Position")); + } + }; + } +} diff --git a/src/entities.rs b/src/entities.rs index d3f36d63..5e838077 100644 --- a/src/entities.rs +++ b/src/entities.rs @@ -191,6 +191,17 @@ pub(crate) struct Entities { len: u32, } +impl Clone for Entities { + fn clone(&self) -> Self { + Self { + meta: self.meta.clone(), + pending: self.pending.clone(), + free_cursor: AtomicIsize::new(self.free_cursor.load(Ordering::Relaxed)), + len: self.len, + } + } +} + impl Entities { /// Reserve entity IDs concurrently /// diff --git a/src/lib.rs b/src/lib.rs index dd077861..98305109 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,6 +73,7 @@ mod batch; mod borrow; mod bundle; mod change_tracker; +mod cloning; mod command_buffer; mod entities; mod entity_builder; @@ -91,6 +92,7 @@ pub use bundle::{ DynamicBundleClone, MissingComponent, }; pub use change_tracker::{ChangeTracker, Changes}; +pub use cloning::{Cloner, TypeUnknownToCloner}; pub use command_buffer::CommandBuffer; pub use entities::{Entity, NoSuchEntity}; pub use entity_builder::{BuiltEntity, BuiltEntityClone, EntityBuilder, EntityBuilderClone}; diff --git a/src/world.rs b/src/world.rs index d6e7bc67..a627e055 100644 --- a/src/world.rs +++ b/src/world.rs @@ -5,7 +5,6 @@ // http://opensource.org/licenses/MIT>, at your option. This file may not be // copied, modified, or distributed except according to those terms. -use crate::alloc::{vec, vec::Vec}; use core::any::TypeId; use core::borrow::Borrow; use core::convert::TryFrom; @@ -20,7 +19,9 @@ use std::error::Error; use hashbrown::hash_map::{Entry, HashMap}; use crate::alloc::boxed::Box; +use crate::alloc::{vec, vec::Vec}; use crate::archetype::{Archetype, TypeIdMap, TypeInfo}; +use crate::cloning::{Cloner, TypeUnknownToCloner}; use crate::entities::{Entities, EntityMeta, Location, ReserveEntitiesIterator}; use crate::query::{assert_borrow, assert_distinct}; use crate::{ @@ -901,6 +902,68 @@ impl World { ArchetypesGeneration(self.archetypes.generation()) } + /// Attempts to clone self using the given [`Cloner`]. + /// + /// This function borrows self mutably for performance so as to avoid runtime borrow checks. + /// + /// The returned [`World`] will be bit-for-bit identical to `self`: + /// * The entities created in each world will receive the same ids. + /// * All Copy components will be bit-for-bit identical. + /// * All Clone components will be bit-for-bit identical to the extent that their Clone implementations are. + /// + /// This differs from creating a new world via [`World::new()`] and manually copying over + /// entities to it: the cloned world will use the same entity handles as the originating world, + /// and so performing the same sequence of operations on the cloned world and the original world + /// will result in the worlds staying in step. This can be useful for rolling back to previous + /// states of the world for e.g. rollback networking. + /// + /// # Example + /// ``` + /// # use hecs::*; + /// // create a world with some things in it + /// let mut world1 = World::new(); + /// let entity_to_drop = world1.spawn((123, "abc".to_owned())); + /// let entity = world1.spawn((456, "def".to_owned())); + /// + /// // drop an entity (which marks its entity handle as available) + /// world1.despawn(entity_to_drop); + /// + /// // create a cloner and register all component types in the world + /// let mut cloner = Cloner::new(); + /// cloner.add_copyable::(); + /// cloner.add_cloneable::(); + /// + /// // actually clone the world + /// let mut world2 = world1.try_clone(&cloner).expect("all used component types are registered"); + /// + /// // 1. entity handles are still valid with the cloned world + /// // 2. component data has been copied to the clone + /// assert_eq!( + /// world1.entity(entity).unwrap().query::<(&i32, &String)>().get(), + /// world2.entity(entity).unwrap().query::<(&i32, &String)>().get() + /// ); + /// + /// // subsequently created entities will be allocated the same entity ids + /// let new1 = world1.spawn((789, "ghi".to_owned())); + /// let new2 = world2.spawn((012, "jkl".to_owned())); + /// assert_eq!(new1, new2); + /// ``` + pub fn try_clone(&mut self, cloner: &Cloner) -> Result { + // clone archetypes first to avoid unnecessary allocations if cloning archetypes fails + let archetypes = self.archetypes.try_clone(cloner)?; + + let cloned = Self { + entities: self.entities.clone(), + archetypes, + bundle_to_archetype: self.bundle_to_archetype.clone(), + insert_edges: self.insert_edges.clone(), + remove_edges: self.remove_edges.clone(), + id: self.id, + }; + + Ok(cloned) + } + /// Number of currently live entities #[inline] pub fn len(&self) -> u32 { @@ -1294,9 +1357,24 @@ impl ArchetypeSet { index, } } + + pub fn try_clone( + &mut self, + cloner: &crate::cloning::Cloner, + ) -> Result { + Ok(Self { + index: self.index.clone(), + archetypes: self + .archetypes + .iter_mut() + .map(|archetype| archetype.try_clone(cloner)) + .collect::>()?, + }) + } } /// Metadata cached for inserting components into entities from this archetype +#[derive(Clone)] struct InsertTarget { /// Components from the current archetype that are replaced by the insert replaced: Vec,