diff --git a/lyra-ecs/src/archetype.rs b/lyra-ecs/src/archetype.rs index 9b431f4..35700c6 100644 --- a/lyra-ecs/src/archetype.rs +++ b/lyra-ecs/src/archetype.rs @@ -1,6 +1,6 @@ use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::DerefMut}; -use crate::{world::ArchetypeEntityId, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick, Entity}; +use crate::{bundle::Bundle, component_info::ComponentInfo, world::ArchetypeEntityId, DynTypeId, Entities, Entity, Record, Tick}; #[derive(Clone)] pub struct ComponentColumn { @@ -295,21 +295,32 @@ impl Archetype { self.capacity = new_cap; } - debug_assert_eq!(self.entity_ids.len(), self.entities.len(), "Somehow the Archetype's entity storage got unsynced"); + self.ensure_synced(); let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64); self.entity_ids.insert(entity, entity_index); self.entities.push(entity); - bundle.take(|data, type_id, _size| { - let col = self.get_column_mut(type_id).unwrap(); - unsafe { col.insert_entity(entity_index.0 as usize, data, *tick); } + bundle.take(|data, type_id, info| { + self.put_component_at(tick, data, type_id, info.layout().size(), entity_index.0 as _); }); entity_index } - /// Removes an entity from the Archetype and frees its components. Returns the entity record - /// that took its place in the component column. + pub(crate) fn put_component_at(&mut self, tick: &Tick, ptr: NonNull, type_id: DynTypeId, size: usize, index: usize) { + let _ = size; + + let col = self.get_column_mut(type_id).unwrap(); + //unsafe { col.set_at(index, ptr, *tick) }; + unsafe { col.insert_entity(index, ptr, *tick); } + } + + /// Removes an entity from the Archetype and frees its components. + /// + /// Inside the component columns, the entities are swap-removed. Meaning that the last + /// entity in the column is moved in the position of the entity that was removed. + /// If there was an entity that was swapped, this function returns the entity, and its + /// new index in the archetype that was put in place of the removed entity. pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> { let entity_index = self.entity_ids.remove(&entity) .expect("The entity is not in this Archetype!"); @@ -343,7 +354,7 @@ impl Archetype { let removed = self.entities.swap_remove(entity_index.0 as _); assert_eq!(removed, entity); - + // now change the ArchetypeEntityId to be the index that the moved entity was moved into. removed_entity.map(|(e, _a)| (e, entity_index)) } @@ -389,6 +400,11 @@ impl Archetype { self.capacity = new_capacity; } + /// Attempts to find the column storing components of `type_id` + pub fn get_column_at(&self, idx: usize) -> Option<&ComponentColumn> { + self.columns.get(idx) + } + /// Attempts to find the column storing components of `type_id` pub fn get_column>(&self, type_id: I) -> Option<&ComponentColumn> { let type_id = type_id.into(); @@ -436,7 +452,7 @@ impl Archetype { /// The entity IS NOT removed from the old archetype. You must manually call [`Archetype::remove_entity`](crate::Archetype). /// It was done this way because I had some borrow check issues when writing [`World::insert`](crate::World) /// related to borrowing mutably from self more than once. - /* pub fn move_into(&self, into_arch: &mut Archetype, entity: Entity, new_components: B) + /* pub fn move_into(&mut self, entities: &mut Entities, tick: &Tick, into_arch: &mut Archetype, entity: Entity, new_components: B) where B: Bundle { @@ -446,25 +462,40 @@ impl Archetype { // move the existing components into the new archetype for col in self.columns.iter() { - let into_col = into_arch.get_column_mut(col.info.type_id).unwrap(); + let into_col = into_arch.get_column_mut(col.info.type_id()).unwrap(); // copy from the old column into the new column, then remove it from the old one unsafe { let ptr = col.borrow_ptr(); let ptr = NonNull::new_unchecked(ptr.as_ptr() - .add(new_index.0 as usize * col.info.layout.size)); - into_col.set_at(new_index.0 as _, ptr); + .add(new_index.0 as usize * col.info.layout().size())); + into_col.set_at(new_index.0 as _, ptr, *tick); + //into_col.set_at(new_index.0 as _, ptr); } } // now move the new components into the new archetype new_components.take(|data, type_id, _size| { let col = into_arch.get_column_mut(type_id).unwrap(); - unsafe { col.set_at(new_index.0 as _, data); } + unsafe { col.set_at(new_index.0 as _, data, *tick); } col.len += 1; }); - //self.remove_entity(entity); + if let Some((en, new_idx)) = self.remove_entity(entity, tick) { + let moved_rec = Record { + id: self.id, + index: new_idx, + }; + entities.insert_entity_record(en, moved_rec); + } + let new_rec = Record { + id: into_arch.id, + index: new_index, + }; + entities.insert_entity_record(entity, new_rec); + + into_arch.ensure_synced(); + self.ensure_synced(); } */ /// Returns a borrow to the map used to find the column indices of the entity. @@ -700,7 +731,7 @@ mod tests { #[test] fn dynamic_archetype() { let layout = Layout::new::(); - let info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", layout); + let info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), layout); let infos = vec![info.clone()]; let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), infos); diff --git a/lyra-ecs/src/bundle.rs b/lyra-ecs/src/bundle.rs index 7e8274a..729e3d7 100644 --- a/lyra-ecs/src/bundle.rs +++ b/lyra-ecs/src/bundle.rs @@ -1,4 +1,4 @@ -use std::{ptr::NonNull, mem::size_of, alloc::Layout}; +use std::{ptr::NonNull, alloc::Layout}; use crate::{component::Component, component_info::ComponentInfo, DynTypeId}; @@ -11,7 +11,7 @@ pub trait Bundle { /// Take the bundle by calling the closure with pointers to each component, its type and size. /// The closure is expected to take ownership of the pointer. - fn take(self, f: impl FnMut(NonNull, DynTypeId, usize)); + fn take(self, f: impl FnMut(NonNull, DynTypeId, ComponentInfo)); /// Returns a boolean indicating if this Bundle is dynamic. See [`DynamicBundle`] fn is_dynamic(&self) -> bool; @@ -26,8 +26,8 @@ impl Bundle for () { vec![ComponentInfo::new::<()>()] } - fn take(self, mut f: impl FnMut(NonNull, DynTypeId, usize)) { - f(NonNull::from(&self).cast(), DynTypeId::of::<()>(), size_of::<()>()); + fn take(self, mut f: impl FnMut(NonNull, DynTypeId, ComponentInfo)) { + f(NonNull::from(&self).cast(), DynTypeId::of::<()>(), ComponentInfo::new::<()>()); } fn is_dynamic(&self) -> bool { @@ -44,8 +44,8 @@ impl Bundle for C { vec![ComponentInfo::new::()] } - fn take(self, mut f: impl FnMut(NonNull, DynTypeId, usize)) { - f(NonNull::from(&self).cast(), DynTypeId::of::(), size_of::()); + fn take(self, mut f: impl FnMut(NonNull, DynTypeId, ComponentInfo)) { + f(NonNull::from(&self).cast(), DynTypeId::of::(), ComponentInfo::new::()); // this must be done to avoid calling drop on heap memory that the component // may manage. So something like a Vec, or HashMap, etc. @@ -70,12 +70,12 @@ macro_rules! impl_bundle_tuple { vec![$(ComponentInfo::new::<$name>()),+] } - fn take(self, mut f: impl FnMut(NonNull, DynTypeId, usize)) { + fn take(self, mut f: impl FnMut(NonNull, DynTypeId, ComponentInfo)) { // these names wont follow rust convention, but its a macro so deal with it let ($($name,)+) = self; $( - f(NonNull::from(&$name).cast(), DynTypeId::of::<$name>(), size_of::<$name>()); + f(NonNull::from(&$name).cast(), DynTypeId::of::<$name>(), ComponentInfo::new::<$name>()); // this must be done to avoid calling drop on heap memory that the component // may manage. So something like a Vec, or HashMap, etc. std::mem::forget($name); @@ -136,8 +136,6 @@ impl DynamicBundle { where C: Component { - let info = ComponentInfo::new::(); - // an owned pointer must be created from the provided component since comp would drop // out of scope and the data would become invalid let ptr = unsafe { @@ -146,17 +144,43 @@ impl DynamicBundle { let layout = Layout::new::(); let alloc_ptr = NonNull::new_unchecked(std::alloc::alloc(layout)).cast::(); std::ptr::copy_nonoverlapping(data.as_ptr(), alloc_ptr.as_ptr(), 1); + + // this must be done to avoid calling drop on heap memory that the component + // may manage. So something like a Vec, or HashMap, etc. + std::mem::forget(comp); alloc_ptr.cast() }; + let info = ComponentInfo::new::(); self.bundle.push((ptr, info)); } - /// Push an unknown type to the bundle + /// Push an unknown type to the end of the bundle. pub fn push_unknown(&mut self, data: NonNull, info: ComponentInfo) { self.bundle.push((data, info)); } + + /// Push a bundle to the end of this dynamic bundle. + pub fn push_bundle(&mut self, bundle: B) + where + B: Bundle + { + bundle.take(|ptr, _, info| { + // unfortunately the components in the bundle must be copied since there is no guarantee that + // `bundle` lasts for as long as the `DynamicBundle`. If the data wasn't copied, the pointers + // could be invalid later. + let p = unsafe { + let alloc_ptr = NonNull::new_unchecked(std::alloc::alloc(info.layout())); + std::ptr::copy_nonoverlapping(ptr.as_ptr(), alloc_ptr.as_ptr(), info.layout().size()); + + alloc_ptr + }; + + self.push_unknown(p, info); + + }); + } } impl Bundle for DynamicBundle { @@ -168,9 +192,9 @@ impl Bundle for DynamicBundle { self.bundle.iter().map(|b| b.1).collect() } - fn take(self, mut f: impl FnMut(NonNull, DynTypeId, usize)) { - for (data, info) in self.bundle.iter() { - f(*data, info.type_id(), info.layout().size()); + fn take(self, mut f: impl FnMut(NonNull, DynTypeId, ComponentInfo)) { + for (data, info) in self.bundle.into_iter() { + f(data, info.type_id(), info); } } diff --git a/lyra-ecs/src/component_info.rs b/lyra-ecs/src/component_info.rs index e363494..57c9e86 100644 --- a/lyra-ecs/src/component_info.rs +++ b/lyra-ecs/src/component_info.rs @@ -67,7 +67,7 @@ impl ComponentInfo { } /// Create ComponentInfo from a type that is not known to rust - pub fn new_unknown(type_id: D, name: &str, layout: Layout) -> Self + pub fn new_unknown(name: Option, type_id: D, layout: Layout) -> Self where D: Into, { diff --git a/lyra-ecs/src/entity.rs b/lyra-ecs/src/entity.rs index b12e04f..d7afd20 100644 --- a/lyra-ecs/src/entity.rs +++ b/lyra-ecs/src/entity.rs @@ -11,6 +11,16 @@ pub struct Entity { pub(crate) generation: u64, } +impl Entity { + pub fn id(&self) -> EntityId { + self.id + } + + pub fn generation(&self) -> u64 { + self.generation + } +} + pub struct Entities { pub(crate) arch_index: HashMap, dead: VecDeque, diff --git a/lyra-ecs/src/query/dynamic/view.rs b/lyra-ecs/src/query/dynamic/view.rs index 107a429..c54e8b7 100644 --- a/lyra-ecs/src/query/dynamic/view.rs +++ b/lyra-ecs/src/query/dynamic/view.rs @@ -111,7 +111,7 @@ mod tests { #[test] fn single_dynamic_view() { let comp_layout = Layout::new::(); - let comp_info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", comp_layout); + let comp_info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), comp_layout); let mut dynamic_bundle = DynamicBundle::default(); let comp = 50u32; diff --git a/lyra-ecs/src/query/filter/mod.rs b/lyra-ecs/src/query/filter/mod.rs index 149de6d..8fe360c 100644 --- a/lyra-ecs/src/query/filter/mod.rs +++ b/lyra-ecs/src/query/filter/mod.rs @@ -2,4 +2,7 @@ pub mod has_component; pub use has_component::*; pub mod or; -pub use or::*; \ No newline at end of file +pub use or::*; + +pub mod not; +pub use not::*; \ No newline at end of file diff --git a/lyra-ecs/src/query/filter/not.rs b/lyra-ecs/src/query/filter/not.rs new file mode 100644 index 0000000..f020415 --- /dev/null +++ b/lyra-ecs/src/query/filter/not.rs @@ -0,0 +1,45 @@ +use std::marker::PhantomData; + +use crate::{query::{AsQuery, Query}, Archetype, World}; + +#[derive(Default)] +pub struct Not { + q: Q, + _marker: PhantomData +} + +impl Copy for Not {} + +impl Clone for Not { + fn clone(&self) -> Self { + Self { + q: self.q.clone(), + _marker: self._marker.clone() + } + } +} + +impl Query for Not { + type Item<'a> = (); + + type Fetch<'a> = (); + + fn new() -> Self { + Not { + q: Q::new(), + _marker: PhantomData + } + } + + fn can_visit_archetype(&self, archetype: &Archetype) -> bool { + !self.q.can_visit_archetype(archetype) + } + + unsafe fn fetch<'a>(&self, _world: &'a World, _: &'a Archetype, _: crate::Tick) -> Self::Fetch<'a> { + () + } +} + +impl AsQuery for Not { + type Query = Self; +} \ No newline at end of file diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 9919a65..ee6ebcd 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -2,7 +2,7 @@ use std::{any::TypeId, collections::HashMap, ptr::NonNull}; use atomic_refcell::{AtomicRef, AtomicRefMut}; -use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewState, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker}; +use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker}; /// The id of the entity for the Archetype. /// @@ -144,37 +144,32 @@ impl World { let current_arch = self.archetypes.get(&record.id).unwrap(); let current_arch_len = current_arch.len(); - let mut col_types: Vec = current_arch.columns.iter().map(|c| c.info.type_id()).collect(); - let orig_col = col_types.clone(); - col_types.extend(bundle.type_ids()); + // contains the type ids for the old component columns + the ids for the new components + let mut combined_column_types: Vec = current_arch.columns.iter().map(|c| c.info.type_id()).collect(); + combined_column_types.extend(bundle.type_ids()); - let mut col_infos: Vec = current_arch.columns.iter().map(|c| c.info).collect(); - col_infos.extend(bundle.info()); + // contains the ComponentInfo for the old component columns + the info for the new components + let mut combined_column_infos: Vec = current_arch.columns.iter().map(|c| c.info).collect(); + combined_column_infos.extend(bundle.info()); - let col_ptrs: Vec<(NonNull, ComponentInfo)> = current_arch.columns.iter() + // pointers only for the old columns + let old_columns: Vec<(NonNull, ComponentInfo)> = current_arch.columns.iter() .map(|c| unsafe { (NonNull::new_unchecked(c.borrow_ptr().as_ptr()), c.info) }) .collect(); - if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&col_types)) { - let res_index = arch.reserve_one(entity); - - for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) { + if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&combined_column_types)) { + let mut dbun = DynamicBundle::new(); + // move old entity components into new archetype columns + for (col_ptr, col_info) in old_columns.into_iter() { unsafe { let ptr = NonNull::new_unchecked(col_ptr.as_ptr() .add(record.index.0 as usize * col_info.layout().size())); - let col = arch.get_column_mut(col_type).unwrap(); - // set_at is used since the entity was reserved - col.set_at(res_index.0 as _, ptr, tick); + dbun.push_unknown(ptr, col_info); } } + dbun.push_bundle(bundle); - bundle.take(|data, type_id, _size| { - let col = arch.get_column_mut(type_id).unwrap(); - // set_at is used since the entity was reserved - unsafe { col.set_at(res_index.0 as _, data, tick); } - }); - - arch.entity_ids.insert(entity, res_index); + let res_index = arch.add_entity(entity, dbun, &tick); arch.ensure_synced(); let new_record = Record { @@ -187,25 +182,24 @@ impl World { // if this entity is the only entity for this archetype, add more columns to it let current_arch = self.archetypes.get_mut(&record.id).unwrap(); current_arch.extend(&tick, vec![bundle]); - return; } let new_arch_id = self.next_archetype_id.increment(); - let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos); - let entity_arch_id = archetype.add_entity(entity, bundle, &tick); + let mut archetype = Archetype::from_bundle_info(new_arch_id, combined_column_infos); - // move the old components into the new archetype - for (column_ptr, column_info) in col_ptrs.into_iter() { + let mut dbun = DynamicBundle::new(); + for (column_ptr, column_info) in old_columns.into_iter() { unsafe { // ptr of component for the entity let comp_ptr = NonNull::new_unchecked(column_ptr.as_ptr() .add(record.index.0 as usize * column_info.layout().size())); - - let col = archetype.get_column_mut(column_info.type_id()).unwrap(); - col.insert_entity(entity_arch_id.0 as _, comp_ptr, tick); + dbun.push_unknown(comp_ptr, column_info); } } + dbun.push_bundle(bundle); + + let entity_arch_id = archetype.add_entity(entity, dbun, &tick); self.archetypes.insert(new_arch_id, archetype); @@ -219,12 +213,103 @@ impl World { } let current_arch = self.archetypes.get_mut(&record.id).unwrap(); - if current_arch.len() > 1 { - current_arch.remove_entity(entity, &tick); - } else if current_arch.len() == 1 { - // The old archetype will only be removed if there was another archetype that would - // work for the entity's components, and the old archetype only had a single entity. - self.archetypes.remove(&record.id).unwrap(); + if let Some((en, enar)) = current_arch.remove_entity(entity, &tick) { + let rec = Record { + id: current_arch.id(), + index: enar + }; + self.entities.insert_entity_record(en, rec); + } + + current_arch.ensure_synced(); + } + + /// A method used for debugging implementation details of the ECS. + /// + /// Here's an example of the output: + /// ``` + /// Entities + /// 1 in archetype 0 at 0 + /// 0 in archetype 1 at 0 + /// 2 in archetype 0 at 1 + /// 3 in archetype 2 at 0 + /// Arch 1 -- 1 entities + /// Col 175564825027445222460146453544114453753 + /// 0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 78 86 0 0 + /// Col 162279302565774655543278578489329315472 + /// 0: 0 0 32 65 0 0 32 65 0 0 32 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0 + /// Col 24291284537013640759061027938209843602 + /// 0: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + /// Arch 2 -- 1 entities + /// Col 175564825027445222460146453544114453753 + /// 0: 0 0 0 0 0 0 0 0 0 0 0 0 237 127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 78 86 0 0 + /// Col 162279302565774655543278578489329315472 + /// 0: 0 0 76 66 0 0 170 66 0 0 136 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0 + /// Col 142862377085187052737282554588643015580 + /// 0: 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + /// Arch 0 -- 2 entities + /// Col 175564825027445222460146453544114453753 + /// 0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0 + /// 1: 0 0 0 0 0 0 0 0 0 0 0 0 237 127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 78 86 0 0 + /// Col 162279302565774655543278578489329315472 + /// 0: 0 0 112 65 0 0 112 65 0 0 112 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0 + /// 1: 0 0 27 67 0 0 184 65 0 0 192 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0 + /// Col 142862377085187052737282554588643015580 + /// 0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + /// 1: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + /// Col 24291284537013640759061027938209843602 + /// 0: 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + /// 1: 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + /// Arch 3 -- 0 entities + /// Col 175564825027445222460146453544114453753 + /// Col 162279302565774655543278578489329315472 + /// ``` + /// + /// This output prints all Entity ids, the archetype they're in, and the index that they're + /// in inside of the archetype. Additionally, the archetypes are printing, including their + /// columns type ids, and the contents of the type ids. This output can be used to debug + /// the contents of entities inside the archetypes. + /// + /// Below is a template of the output: + /// ``` + /// Entities + /// %ENTITY_ID% in archetype %ARCHETYPE_ID% at %INDEX% + /// Arch ID -- %ARCHETYPE_LEN% entities + /// %FOR EACH COL% + /// Col COLUMN_COMPONENT_TYPE_ID + /// %FOR EACH ENTITY% + /// %ENTITY_INDEX%: %COMPONENT_BYTES% + /// ``` + /// If the template above doesn't help you in understanding the output, read the source code + /// of the function. The source code is pretty simple. + pub fn debug_print_world(&self) { + println!("Entities"); + for (en, rec) in &self.entities.arch_index { + println!(" {} in archetype {} at {}", en.0, rec.id.0, rec.index.0); + } + + for arch in self.archetypes.values() { + println!("Arch {} -- {} entities", arch.id().0, arch.len()); + + for col in &arch.columns { + // no clue if doing this is stable, but this is a debug function so :shrug: + let tyid: u128 = unsafe { std::mem::transmute(col.info.type_id().as_rust()) }; + println!(" Col {}", tyid); + + for en in 0..col.len { + // get the ptr starting at the component + let p = col.borrow_ptr(); + let p = unsafe { p.as_ptr().add(en * col.info.layout().size()) }; + + print!(" {}: ", en); + // print each byte of the component + for i in 0..col.info.layout().size() { + let d = unsafe { *p.add(i) }; + print!("{} ", d); + } + println!(); + } + } } } @@ -364,6 +449,10 @@ impl World { self.resources.get(&TypeId::of::()) .map(|d| unsafe { NonNull::new_unchecked(d.data.as_ptr() as *mut T) }) } + + pub fn archetype_count(&self) -> usize { + self.archetypes.len() + } } // TODO: Ensure that all non-send resources are only accessible on the main thread.