diff --git a/Cargo.lock b/Cargo.lock index 4e0b9c0..d988310 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,18 +23,11 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - [[package]] name = "gecs" version = "0.3.0" dependencies = [ "gecs_macros", - "itertools", "seq-macro", ] @@ -51,15 +44,6 @@ dependencies = [ "xxhash-rust", ] -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "memoffset" version = "0.9.1" diff --git a/Cargo.toml b/Cargo.toml index 96afacf..0456c8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,3 @@ wrapping_entity_raw_version = [] gecs_macros = { version = "0.3.0", path = "macros", default_features = false } seq-macro = { version = "0.3.5" } # For building "variadic" storage -itertools = { version = "0.13.0" } # For zipping slices in archetypes diff --git a/macros/src/data.rs b/macros/src/data.rs index fc90b44..8f28a29 100644 --- a/macros/src/data.rs +++ b/macros/src/data.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use base64::Engine as _; use speedy::{Readable, Writable}; -use syn::{Expr, Ident}; +use syn::Ident; -use crate::parse::{HasAttributeId, ParseAttributeCfg, ParseCapacity, ParseEcsFinalize}; +use crate::parse::{HasAttributeId, ParseAttributeCfg, ParseEcsFinalize}; #[derive(Debug, Readable, Writable)] pub struct DataWorld { @@ -30,15 +30,7 @@ pub struct DataComponent { } #[derive(Debug)] -pub struct DataArchetypeBuildOnly { - pub capacity: DataCapacity, -} - -#[derive(Debug)] -pub enum DataCapacity { - Fixed(Expr), - Dynamic, -} +pub struct DataArchetypeBuildOnly; impl DataWorld { pub fn new(mut parse: ParseEcsFinalize) -> syn::Result { @@ -82,9 +74,7 @@ impl DataWorld { }); } - let build_data = DataArchetypeBuildOnly { - capacity: convert_capacity(archetype.capacity), - }; + let build_data = DataArchetypeBuildOnly; archetypes.push(DataArchetype { id: last_archetype_id.unwrap(), @@ -126,13 +116,6 @@ impl DataArchetype { } } -fn convert_capacity(capacity: ParseCapacity) -> DataCapacity { - match capacity { - ParseCapacity::Fixed(expr) => DataCapacity::Fixed(expr), - ParseCapacity::Dynamic => DataCapacity::Dynamic, - } -} - fn evaluate_cfgs(cfg_data: &HashMap, cfgs: &[ParseAttributeCfg]) -> bool { for cfg in cfgs { let predicate = cfg.predicate.to_string(); diff --git a/macros/src/generate/world.rs b/macros/src/generate/world.rs index 98b2c36..5a3c124 100644 --- a/macros/src/generate/world.rs +++ b/macros/src/generate/world.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use xxhash_rust::xxh3::xxh3_128; -use crate::data::{DataArchetype, DataCapacity, DataWorld}; +use crate::data::{DataArchetype, DataWorld}; use crate::generate::util::to_snake; #[allow(non_snake_case)] // Allow for type-like names to make quote!() clearer @@ -19,6 +19,7 @@ pub fn generate_world(world_data: &DataWorld, raw_input: &str) -> TokenStream { // Types and traits let World = format_ident!("{}", world_data.name); + let WorldCapacity = format_ident!("{}Capacity", world_data.name); let ArchetypeSelectId = format_ident!("ArchetypeSelectId"); let ArchetypeSelectEntity = format_ident!("ArchetypeSelectEntity"); @@ -72,7 +73,13 @@ pub fn generate_world(world_data: &DataWorld, raw_input: &str) -> TokenStream { quote!( #( pub use #ecs_world_sealed::#Archetype; )* - pub use #ecs_world_sealed::{#World, #ArchetypeSelectId, #ArchetypeSelectEntity, #ArchetypeSelectEntityRaw}; + pub use #ecs_world_sealed::{ + #World, + #WorldCapacity, + #ArchetypeSelectId, + #ArchetypeSelectEntity, + #ArchetypeSelectEntityRaw + }; #[doc(hidden)] pub use #ecs_world_sealed::{#ArchetypeSelectInternalWorld}; @@ -93,6 +100,12 @@ pub fn generate_world(world_data: &DataWorld, raw_input: &str) -> TokenStream { )* } + pub struct #WorldCapacity { + #( + pub #with_capacity_param, + )* + } + impl #World { /// Creates a new empty world. /// @@ -111,17 +124,15 @@ pub fn generate_world(world_data: &DataWorld, raw_input: &str) -> TokenStream { /// Creates a new world with per-archetype capacities. /// - /// Expects a capacity value for every dynamic archetype. - /// - /// This will allocate all archetypes to either their fixed size, or the given - /// dynamic capacity. If a given dynamic capacity is 0, that archetype will not - /// allocate until an entity is created in it. + /// This will allocate all archetypes to the given dynamic capacity. If a + /// given dynamic capacity is 0, that archetype will not allocate until an + /// entity is created in it. /// /// # Panics /// /// This will panic if given a size that exceeds the maximum possible capacity /// value for an archetype (currently `16,777,216`). - pub fn with_capacity(#(#with_capacity_param)*) -> Self { + pub fn with_capacity(capacity: #WorldCapacity) -> Self { Self { #( #archetype: #Archetype::#with_capacity_new, )* } @@ -485,33 +496,12 @@ fn section_archetype(archetype_data: &DataArchetype) -> TokenStream { let SlicesN = format_ident!("Slices{}", count_str); let ContentArgs = quote!(#Archetype, #(#Component),*); - let (StorageN, BorrowN, StorageArgs) = - match &archetype_data.build_data.as_ref().unwrap().capacity { - DataCapacity::Fixed(expr) => ( - format_ident!("StorageFixed{}", count_str), - format_ident!("BorrowFixed{}", count_str), - quote!(#Archetype, #(#Component,)* { #expr }), - ), - DataCapacity::Dynamic => ( - format_ident!("StorageDynamic{}", count_str), - format_ident!("BorrowDynamic{}", count_str), - quote!(#Archetype, #(#Component,)*), - ), - }; + let StorageN = format_ident!("Storage{}", count_str); + let BorrowN = format_ident!("Borrow{}", count_str); + let StorageArgs = quote!(#Archetype, #(#Component,)*); - // Generated subsections - let with_capacity = match &archetype_data.build_data.as_ref().unwrap().capacity { - DataCapacity::Fixed(_) => quote!(), - DataCapacity::Dynamic => quote!( - /// Constructs a new archetype pre-allocated to the given storage capacity. - /// - /// If the given capacity would result in zero size, this will not allocate. - #[inline(always)] - pub fn with_capacity(capacity: usize) -> Self { - Self { data: #StorageN::with_capacity(capacity) } - } - ), - }; + let IterArgs = quote!(&Entity<#Archetype>, #(&#Component,)*); + let IterMutArgs = quote!(&Entity<#Archetype>, #(&mut #Component,)*); // Function names let get_slice = (0..count) @@ -563,7 +553,13 @@ fn section_archetype(archetype_data: &DataArchetype) -> TokenStream { Self { data: #StorageN::new() } } - #with_capacity // Only generated for dynamic storage + /// Constructs a new archetype pre-allocated to the given storage capacity. + /// + /// If the given capacity would result in zero size, this will not allocate. + #[inline(always)] + pub fn with_capacity(capacity: usize) -> Self { + Self { data: #StorageN::with_capacity(capacity) } + } /// Returns the number of entities in the archetype, also referred to as its length. #[inline(always)] @@ -589,7 +585,7 @@ fn section_archetype(archetype_data: &DataArchetype) -> TokenStream { /// Returns the generational version of the archetype. Intended for internal use. #[inline(always)] - pub const fn version(&self) -> VersionArchetype { + pub const fn version(&self) -> ArchetypeVersion { self.data.version() } @@ -641,6 +637,18 @@ fn section_archetype(archetype_data: &DataArchetype) -> TokenStream { self.data.remove(entity) } + /// Returns an iterator over all of the entities and their data. + #[inline(always)] + pub fn iter(&mut self) -> impl Iterator { + self.data.iter() + } + + /// Returns a mutable iterator over all of the entities and their data. + #[inline(always)] + pub fn iter_mut(&mut self) -> impl Iterator { + self.data.iter_mut() + } + /// Begins a borrow context for the given entity on this archetype. This will allow /// direct access to that entity's components with runtime borrow checking. This can /// be faster than accessing the components as slices, as it will skip bounds checks. @@ -838,22 +846,6 @@ fn section_archetype(archetype_data: &DataArchetype) -> TokenStream { } } - impl<'a> #ArchetypeSlices<'a> { - #[inline(always)] - pub fn zipped( - &'a self, - ) -> impl Iterator, #(&'a #Component),*)> { - ::gecs::__internal::izip!(self.entity.iter(), #(self.#component.iter()),*) - } - - #[inline(always)] - pub fn zipped_mut( - &'a mut self, - ) -> impl Iterator, #(&'a mut #Component),*)> { - ::gecs::__internal::izip!(self.entity.iter(), #(self.#component.iter_mut()),*) - } - } - impl<'a> ArchetypeCanResolve<'a, #ArchetypeView<'a>, Entity<#Archetype>> for #Archetype { #[inline(always)] fn resolve_for(&self, key: Entity<#Archetype>) -> Option { @@ -882,18 +874,12 @@ fn section_archetype(archetype_data: &DataArchetype) -> TokenStream { #[allow(non_snake_case)] fn with_capacity_param(archetype_data: &DataArchetype) -> TokenStream { - let archetype_capacity = format_ident!("capacity_{}", to_snake(&archetype_data.name)); - match archetype_data.build_data.as_ref().unwrap().capacity { - DataCapacity::Fixed(_) => quote!(), - DataCapacity::Dynamic => quote!(#archetype_capacity: usize,), - } + let archetype = format_ident!("{}", to_snake(&archetype_data.name)); + quote!(#archetype: usize) } #[allow(non_snake_case)] fn with_capacity_new(archetype_data: &DataArchetype) -> TokenStream { - let archetype_capacity = format_ident!("capacity_{}", to_snake(&archetype_data.name)); - match archetype_data.build_data.as_ref().unwrap().capacity { - DataCapacity::Fixed(_) => quote!(new()), - DataCapacity::Dynamic => quote!(with_capacity(#archetype_capacity)), - } + let archetype = format_ident!("{}", to_snake(&archetype_data.name)); + quote!(with_capacity(capacity.#archetype)) } diff --git a/macros/src/parse/world.rs b/macros/src/parse/world.rs index 321e633..f310e94 100644 --- a/macros/src/parse/world.rs +++ b/macros/src/parse/world.rs @@ -4,8 +4,8 @@ use proc_macro2::{Span, TokenStream}; use quote::format_ident; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::token::{Comma, Dyn, Semi}; -use syn::{braced, bracketed, parenthesized, Expr, Ident, LitBool, LitInt, Token}; +use syn::token::{Comma, Semi}; +use syn::{braced, bracketed, parenthesized, Ident, LitBool, LitInt, Token}; use super::*; @@ -52,7 +52,6 @@ pub struct ParseArchetype { pub cfgs: Vec, pub id: Option, pub name: Ident, - pub capacity: ParseCapacity, pub components: Vec, } @@ -86,12 +85,6 @@ pub enum ParseAttributeData { ComponentId(ParseAttributeId), } -#[derive(Debug)] -pub enum ParseCapacity { - Fixed(Expr), - Dynamic, -} - impl ParseEcsWorld { pub fn collect_all_cfg_predicates(&self) -> Vec { let mut filter = HashSet::new(); @@ -227,8 +220,6 @@ impl Parse for ParseArchetype { let name: Ident = content.parse()?; - content.parse::()?; - let capacity: ParseCapacity = content.parse()?; content.parse::()?; let components: Vec = @@ -242,7 +233,6 @@ impl Parse for ParseArchetype { cfgs, id: None, name, - capacity, components, }) } @@ -345,18 +335,6 @@ impl Parse for ParseAttribute { } } -impl Parse for ParseCapacity { - fn parse(input: ParseStream) -> syn::Result { - let lookahead = input.lookahead1(); - if lookahead.peek(Dyn) { - input.parse::()?; - Ok(ParseCapacity::Dynamic) - } else { - input.parse().map(ParseCapacity::Fixed) - } - } -} - impl HasAttributeId for ParseArchetype { fn name(&self) -> &Ident { &self.name diff --git a/src/archetype/iter.rs b/src/archetype/iter.rs new file mode 100644 index 0000000..014d66d --- /dev/null +++ b/src/archetype/iter.rs @@ -0,0 +1,75 @@ +use seq_macro::seq; + +use crate::entity::Entity; +use crate::traits::Archetype; + +macro_rules! declare_iter_n { + ($iter:ident, $iter_mut:ident, $n:literal) => { + seq!(I in 0..$n { + pub struct $iter<'a, A: Archetype, #(T~I,)*> { + pub(crate) remaining: usize, + pub(crate) ptr_entity: *const Entity, + #(pub(crate) ptr_d~I: *const T~I,)* + pub(crate) phantom: std::marker::PhantomData<&'a mut A>, + } + + pub struct $iter_mut<'a, A: Archetype, #(T~I,)*> { + pub(crate) remaining: usize, + pub(crate) ptr_entity: *const Entity, + #(pub(crate) ptr_d~I: *mut T~I,)* + pub(crate) phantom: std::marker::PhantomData<&'a mut A>, + } + + impl<'a, A: Archetype + 'a, #(T~I: 'a,)*> Iterator for $iter<'a, A, #(T~I,)*> { + type Item = (&'a Entity, #(&'a T~I,)*); + + fn next(&mut self) -> Option { + if self.remaining == 0 { + return None; + } + + unsafe { + let result = (&*self.ptr_entity, #(&*self.ptr_d~I,)*); + + self.ptr_entity = self.ptr_entity.offset(1); + #(self.ptr_d~I = self.ptr_d~I.offset(1);)* + + self.remaining -= 1; + Some(result) + } + } + } + + impl<'a, A: Archetype + 'a, #(T~I: 'a,)*> Iterator for $iter_mut<'a, A, #(T~I,)*> { + type Item = (&'a Entity, #(&'a mut T~I,)*); + + fn next(&mut self) -> Option { + if self.remaining == 0 { + return None; + } + + unsafe { + let result = (&*self.ptr_entity, #(&mut *self.ptr_d~I,)*); + + self.ptr_entity = self.ptr_entity.offset(1); + #(self.ptr_d~I = self.ptr_d~I.offset(1);)* + + self.remaining -= 1; + Some(result) + } + } + } + }); + }; +} + +// Declare entries for up to 16 components. +seq!(N in 1..=16 { + declare_iter_n!(Iter~N, IterMut~N, N); +}); + +// Declare additional entries for up to 32 components. +#[cfg(feature = "32_components")] +seq!(N in 17..=32 { + declare_iter_n!(Iter~N, IterMut~N, N); +}); diff --git a/src/archetype/mod.rs b/src/archetype/mod.rs index 8216a4e..516457e 100644 --- a/src/archetype/mod.rs +++ b/src/archetype/mod.rs @@ -1,5 +1,5 @@ +pub(crate) mod iter; pub(crate) mod slices; pub(crate) mod slot; -pub(crate) mod storage_dynamic; -pub(crate) mod storage_fixed; +pub(crate) mod storage; pub(crate) mod view; diff --git a/src/archetype/slot.rs b/src/archetype/slot.rs index 1e6cbcc..366a052 100644 --- a/src/archetype/slot.rs +++ b/src/archetype/slot.rs @@ -1,8 +1,7 @@ use std::mem::MaybeUninit; -use crate::index::{DataIndex, MAX_DATA_CAPACITY, MAX_DATA_INDEX}; -use crate::util::{num_assert_leq, num_assert_lt}; -use crate::version::VersionSlot; +use crate::index::{TrimmedIndex, MAX_DATA_INDEX}; +use crate::version::SlotVersion; // We use the highest order bit to mark which indices are free list. // This is necessary because we need to catch if a bad entity handle @@ -15,122 +14,113 @@ const FREE_BIT: u32 = 1 << 31; // This index has the FREE_BIT baked into it. const FREE_LIST_END: u32 = (FREE_BIT - 1) | FREE_BIT; -#[inline(always)] -pub(crate) fn populate_free_list( - free_list_start: DataIndex, // Index in the full slot array of where to begin - slots: &mut [MaybeUninit], // Complete slot array, including old slots -) -> SlotIndex { - // We need to make sure that MAX_DATA_CAPACITY wouldn't overflow a u32. - num_assert_leq!(MAX_DATA_CAPACITY as usize, u32::MAX as usize); - // Make sure we aren't trying to populate more slots than we could store. - if slots.len() > MAX_DATA_CAPACITY as usize { - panic!("capacity may not exceed {}", MAX_DATA_CAPACITY); - } - - if slots.len() > 0 { - let start_index = free_list_start.get() as usize; - let last_index = slots.len() - 1; - - debug_assert!(start_index <= slots.len()); - - for i in start_index..(slots.len() - 1) { - unsafe { - // SAFETY: We know i is less than MAX_DATA_CAPACITY and won't - // overflow because we only go up to slots.len() - 1, and here - // we also know that slots.len() <= MAX_DATA_CAPACITY <= u32::MAX. - let index = DataIndex::new_unchecked(i.wrapping_add(1) as u32); - let slot = Slot::new_free(SlotIndex::new_free(index)); - - // SAFETY: We know that i < slots.len() and is safe to write to. - slots.get_unchecked_mut(i).write(slot); - } - } - - // Set the last slot to point off the end of the free list. - let last_slot = Slot::new_free(SlotIndex::new_free_end()); - // SAFETY: We know that last_index is valid and less than slots.len(). - unsafe { slots.get_unchecked_mut(last_index).write(last_slot) }; - - // Point the free list head to the front of the list. - SlotIndex::new_free(free_list_start) - } else { - // Otherwise, we have nothing, so point the free list head to the end. - SlotIndex::new_free_end() - } -} - /// The data index stored in a slot. /// -/// This can point to entity data, or be a member of the slot free list. -#[derive(Clone, Copy)] +/// Can point to the dense list (entity data) if the slot is live, or to +/// the sparse list (other slots) if the slot is a member of the free list. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct SlotIndex(u32); impl SlotIndex { - /// Creates a new SlotIndex at the end of the free list. + /// Assigns this index to some non-free data index. + /// This may be a reassignment of an already live slot. #[inline(always)] - pub(crate) fn new_free_end() -> Self { - Self(FREE_LIST_END) + pub(crate) fn new_data(index: TrimmedIndex) -> Self { + Self(Into::::into(index)) } - /// Creates a new SlotIndex pointing to another slot in the free list. - pub(crate) fn new_free(next_free: DataIndex) -> Self { - Self(FREE_BIT | next_free.get()) + /// Assigns this index to some free slot index. + #[inline(always)] + pub(crate) fn new_free(index: TrimmedIndex) -> Self { + // Make sure that there is room in the index for the free bit. + const { assert!((MAX_DATA_INDEX as usize) < (FREE_BIT as usize)) }; + + Self(Into::::into(index) | FREE_BIT) } - /// Returns true if this slot is freed. + /// Creates a new SlotIndex at the end of the free list. #[inline(always)] - pub(crate) fn is_free(&self) -> bool { + pub(crate) const fn free_end() -> Self { + Self(FREE_LIST_END) + } + + /// Returns true if this slot index points to the free list. + #[inline(always)] + pub(crate) const fn is_free(&self) -> bool { (FREE_BIT & self.0) != 0 } /// Returns true if this is points to the end of the free list. #[inline(always)] - pub(crate) fn is_free_list_end(&self) -> bool { + pub(crate) const fn is_free_end(&self) -> bool { self.0 == FREE_LIST_END } - /// Returns the data index this slot points to. - /// Will be `None` if the index is a free list index. + /// Returns the data index this slot points to, if valid (e.g. not free). #[inline(always)] - pub(crate) fn get_data(&self) -> Option { + pub(crate) fn index_data(&self) -> Option { debug_assert!(self.is_free() == false); - DataIndex::new_u32(self.0) + match self.is_free_end() { + true => None, + // SAFETY: If this isn't free, then we know it must be a valid `TrimmedIndex` + false => unsafe { Some(TrimmedIndex::new_u32(self.0).unwrap_unchecked()) }, + } } - /// Returns the free list index this slot points to. - /// Will be `None` if the index is the free list end. + /// Returns the free list entry this slot points to, if valid (e.g. not free list end). #[inline(always)] - pub(crate) fn get_next_free(&self) -> Option { - // This only works if (!FREE_BIT & FREE_LIST_END) is too big to fit in a - // DataIndex. We test this in verify_free_list_end_is_invalid_data_index. - + pub(crate) fn index_free(&self) -> Option { debug_assert!(self.is_free()); - DataIndex::new_u32(!FREE_BIT & self.0) - } - - /// Assigns a slot to some non-free data index. - /// This may be a reassignment of an already live slot. - #[inline(always)] - pub(crate) fn assign_data(&mut self, index_data: DataIndex) { - self.0 = index_data.get(); + match self.is_free_end() { + true => None, + // SAFETY: If this isn't the free end, then we know it must be a valid `TrimmedIndex` + false => unsafe { Some(TrimmedIndex::new_u32(self.0 & !FREE_BIT).unwrap_unchecked()) }, + } } } +// TODO: Seal this #[derive(Clone, Copy)] -pub(crate) struct Slot { +pub struct Slot { index: SlotIndex, - version: VersionSlot, + version: SlotVersion, } impl Slot { + pub(crate) fn populate_free_list( + start: TrimmedIndex, // Index of where the unset section of the slot array begins + slots: &mut [MaybeUninit], // Complete slot array, including old slots + ) -> SlotIndex { + if slots.len() > 0 { + let start_idx = start.into(); + let end_idx = slots.len() - 1; + + // Go to the second-to-last slot + for idx in start_idx..end_idx { + let next = TrimmedIndex::new_usize(idx + 1).unwrap(); + let slot = Slot::new_free(SlotIndex::new_free(next)); + slots.get_mut(idx).unwrap().write(slot); + } + + // Set the last slot to point off the end of the free list. + let last_slot = Slot::new_free(SlotIndex::free_end()); + slots.get_mut(end_idx).unwrap().write(last_slot); + + // Point the free list head to the front of the list. + SlotIndex::new_free(start) + } else { + // Otherwise, we have nothing, so point the free list head to the end. + SlotIndex::free_end() + } + } + #[inline(always)] pub(crate) fn new_free(next_free: SlotIndex) -> Self { - // Make sure that there is room in the index for the free bit. - num_assert_lt!(MAX_DATA_INDEX as usize, FREE_BIT as usize); + debug_assert!(next_free.is_free()); Self { index: next_free, - version: VersionSlot::start(), + version: SlotVersion::start(), } } @@ -148,14 +138,14 @@ impl Slot { /// Get the slot's generational version. #[inline(always)] - pub(crate) fn version(&self) -> VersionSlot { + pub(crate) fn version(&self) -> SlotVersion { self.version } /// Assigns a slot to some data. This does not increment the version. #[inline(always)] - pub(crate) fn assign(&mut self, index_data: DataIndex) { - self.index.assign_data(index_data); + pub(crate) fn assign(&mut self, index_data: TrimmedIndex) { + self.index = SlotIndex::new_data(index_data); // NOTE: We increment the version on release, not assignment. } @@ -174,5 +164,5 @@ impl Slot { // If this isn't true, then we can't trust the FREE_LIST_END value. #[test] fn verify_free_list_end_is_invalid_data_index() { - assert!(DataIndex::new_u32(!FREE_BIT & FREE_LIST_END).is_none()); + assert!(TrimmedIndex::new_u32(!FREE_BIT & FREE_LIST_END).is_none()); } diff --git a/src/archetype/storage_dynamic.rs b/src/archetype/storage.rs similarity index 88% rename from src/archetype/storage_dynamic.rs rename to src/archetype/storage.rs index 5d127be..12c54ff 100644 --- a/src/archetype/storage_dynamic.rs +++ b/src/archetype/storage.rs @@ -1,32 +1,42 @@ use std::alloc::{self, Layout}; use std::cell::{Ref, RefCell, RefMut}; +use std::marker::PhantomData; use std::mem::{self, MaybeUninit}; use std::ptr::{self, NonNull}; use std::slice; use seq_macro::seq; +use crate::archetype::iter::*; use crate::archetype::slices::*; -use crate::archetype::slot::{self, Slot, SlotIndex}; +use crate::archetype::slot::{Slot, SlotIndex}; use crate::archetype::view::*; use crate::entity::{Entity, EntityRaw}; -use crate::index::{DataIndex, MAX_DATA_CAPACITY}; +use crate::index::{TrimmedIndex, MAX_DATA_CAPACITY}; use crate::traits::{Archetype, EntityKey, StorageCanResolve}; use crate::util::{debug_checked_assume, num_assert_leq}; -use crate::version::VersionArchetype; +use crate::version::ArchetypeVersion; macro_rules! declare_storage_dynamic_n { - ($name:ident, $borrow:ident, $slices:ident, $view:ident, $n:literal) => { + ( + $name:ident, + $borrow:ident, + $iter:ident, + $iter_mut:ident, + $slices:ident, + $view:ident, + $n:literal + ) => { seq!(I in 0..$n { pub struct $name { - version: VersionArchetype, + version: ArchetypeVersion, len: usize, capacity: usize, free_head: SlotIndex, - slots: DataDynamic, // Sparse + slots: DataPtr, // Sparse // No RefCell here since we never grant mutable access externally - entities: DataDynamic>, - #(d~I: RefCell>,)* + entities: DataPtr>, + #(d~I: RefCell>,)* } impl $name @@ -45,19 +55,19 @@ macro_rules! declare_storage_dynamic_n { panic!("capacity may not exceed {}", MAX_DATA_CAPACITY); } - let mut slots: DataDynamic = DataDynamic::with_capacity(capacity); + let mut slots: DataPtr = DataPtr::with_capacity(capacity); // SAFETY: We just allocated the slot array with this capacity. let raw_data = unsafe { slots.raw_data(capacity) }; - let free_head = slot::populate_free_list(DataIndex::zero(), raw_data); + let free_head = Slot::populate_free_list(TrimmedIndex::zero(), raw_data); Self { - version: VersionArchetype::start(), + version: ArchetypeVersion::start(), len: 0, capacity, free_head, slots, - entities: DataDynamic::with_capacity(capacity), - #(d~I: RefCell::new(DataDynamic::with_capacity(capacity)),)* + entities: DataPtr::with_capacity(capacity), + #(d~I: RefCell::new(DataPtr::with_capacity(capacity)),)* } } @@ -78,7 +88,7 @@ macro_rules! declare_storage_dynamic_n { /// The overall version for this data structure. Used for raw indices. #[inline(always)] - pub const fn version(&self) -> VersionArchetype { + pub const fn version(&self) -> ArchetypeVersion { self.version } @@ -90,7 +100,7 @@ macro_rules! declare_storage_dynamic_n { if self.len >= self.capacity() { // If we're full, we should also be at the end of the slot free list. - debug_assert!(self.free_head.is_free_list_end()); + debug_assert!(self.free_head.is_free_end()); if self.grow() == false { return None; // Out of room to grow @@ -99,15 +109,15 @@ macro_rules! declare_storage_dynamic_n { unsafe { // SAFETY: We will never hit the the free list end if we're below capacity - let slot_index = self.free_head.get_next_free().unwrap_unchecked(); + let slot_index = self.free_head.index_free().unwrap_unchecked(); // SAFETY: We never let self.len be greater than MAX_DATA_CAPACITY. - let dense_index = DataIndex::new_unchecked(self.len as u32); + let dense_index = TrimmedIndex::new_usize(self.len).unwrap_unchecked(); // SAFETY: We know that the slot storage is valid up to our capacity. let slots = self.slots.slice_mut(self.capacity()); // SAFETY: We know this is not the end of the free list, and we know that // a free list slot index can never be assigned to an out of bounds value. - let slot = slots.get_unchecked_mut(slot_index.get() as usize); + let slot = slots.get_unchecked_mut(Into::::into(slot_index)); // NOTE: Do not change the following order of operations! debug_assert!(slot.is_free()); @@ -150,8 +160,8 @@ macro_rules! declare_storage_dynamic_n { let result = unsafe { // SAFETY: These are guaranteed by resolve_slot to be in range. - let slot_index_usize: usize = slot_index.get() as usize; - let dense_index_usize: usize = dense_index.get() as usize; + let slot_index_usize: usize = slot_index.into(); + let dense_index_usize: usize = dense_index.into(); debug_assert!(self.len > 0); debug_assert!(slot_index_usize <= self.capacity()); @@ -167,7 +177,7 @@ macro_rules! declare_storage_dynamic_n { // SAFETY: We know the entity slice has a length of self.len. let last_entity = *entities.get_unchecked(last_dense_index); // SAFETY: We guarantee that stored entities point to valid slots. - let last_slot_index: usize = last_entity.slot_index().get() as usize; + let last_slot_index: usize = last_entity.slot_index().into(); // Perform the swap_remove on our data to drop the target entity. // SAFETY: We guarantee that non-free slots point to valid dense data. @@ -227,6 +237,34 @@ macro_rules! declare_storage_dynamic_n { } } + /// Returns an iterator over all of the entities and their data. + #[inline] + pub fn iter(&mut self) -> impl Iterator, #(&T~I,)*)> { + unsafe { + // SAFETY: We've initialized all data by this point and won't exceed self.len. + $iter { + remaining: self.len, + ptr_entity: self.entities.ptr_data(), + #(ptr_d~I: self.d~I.get_mut().ptr_data(),)* + phantom: PhantomData, + } + } + } + + /// Returns a mutable iterator over all of the entities and their data. + #[inline] + pub fn iter_mut(&mut self) -> impl Iterator, #(&mut T~I,)*)> { + unsafe { + // SAFETY: We've initialized all data by this point and won't exceed self.len. + $iter_mut { + remaining: self.len, + ptr_entity: self.entities.ptr_data(), + #(ptr_d~I: self.d~I.get_mut().ptr_data(),)* + phantom: PhantomData, + } + } + } + /// Populates a view struct with our stored data for the given entity key. #[inline(always)] pub fn get_view_mut<'a, E: $view<'a, A, #(T~I,)*>, K: EntityKey>( @@ -322,7 +360,7 @@ macro_rules! declare_storage_dynamic_n { /// Resolves the slot index and data index for a given entity. /// Both indices are guaranteed to point to valid cells. #[inline(always)] - fn resolve_slot(&self, entity: Entity) -> Option<(DataIndex, DataIndex)> { + fn resolve_slot(&self, entity: Entity) -> Option<(TrimmedIndex, TrimmedIndex)> { // Nothing to resolve if we have nothing stored if self.len == 0 { return None; @@ -332,7 +370,7 @@ macro_rules! declare_storage_dynamic_n { let slot_index = entity.slot_index(); unsafe { - let slot_index_usize = slot_index.get() as usize; + let slot_index_usize: usize = slot_index.into(); // NOTE: It's a little silly, but we don't actually know if this entity // was created by this map, so we can't assume internal consistency here. @@ -356,7 +394,7 @@ macro_rules! declare_storage_dynamic_n { } // SAFETY: We know that this is not a free slot due to the check above. - let dense_index = slot.index().get_data().unwrap_unchecked(); + let dense_index = slot.index().index_data().unwrap_unchecked(); Some((slot_index, dense_index)) } @@ -382,15 +420,15 @@ macro_rules! declare_storage_dynamic_n { self.entities.grow(self.capacity, new_capacity); #(self.d~I.get_mut().grow(self.capacity, new_capacity);)* - // SAFETY: We know self.len < MAX_DATA_CAPACITY. - let free_list_start = DataIndex::new_unchecked(self.len as u32); + // SAFETY: We know self.len <= MAX_DATA_CAPACITY. + let free_start = TrimmedIndex::new_usize(self.len).unwrap_unchecked(); // SAFETY: We just grew the slot data array up to new_capacity. let slots = self.slots.raw_data(new_capacity); // Populate the end of the list as the new free list. We are // assuming here that, because we are full, every slot is occupied // and so our free list is entirely empty. Thus, we need a new one. - self.free_head = slot::populate_free_list(free_list_start, slots); + self.free_head = Slot::populate_free_list(free_start, slots); // Update our capacity self.capacity = new_capacity; @@ -410,7 +448,7 @@ macro_rules! declare_storage_dynamic_n { Some(found) => found, }; - let dense_index_usize = dense_index.get() as usize; + let dense_index_usize = dense_index.into(); unsafe { // SAFETY: This is checked when we create and grow. @@ -426,7 +464,7 @@ macro_rules! declare_storage_dynamic_n { impl StorageCanResolve> for $name { #[inline(always)] fn resolve_for(&self, raw: EntityRaw) -> Option { - let dense_index = raw.dense_index().get() as usize; + let dense_index = raw.dense_index().into(); // We need to guarantee that the resulting index is in bounds. match (raw.version() == self.version()) && (dense_index < self.len) { @@ -530,21 +568,37 @@ macro_rules! declare_storage_dynamic_n { // Declare storage for up to 16 components. seq!(N in 1..=16 { - declare_storage_dynamic_n!(StorageDynamic~N, BorrowDynamic~N, Slices~N, View~N, N); + declare_storage_dynamic_n!( + Storage~N, + Borrow~N, + Iter~N, + IterMut~N, + Slices~N, + View~N, + N + ); }); // Declare additional storage for up to 32 components. #[cfg(feature = "32_components")] seq!(N in 17..=32 { - declare_storage_dynamic_n!(StorageDynamic~N, BorrowDynamic~N, Slices~N, View~N, N); + declare_storage_dynamic_n!( + Storage~N, + Borrow~N, + Iter~N, + IterMut~N, + Slices~N, + View~N, + N + ); }); -pub struct DataDynamic(NonNull>); +pub struct DataPtr(NonNull>); -unsafe impl Send for DataDynamic where T: Send {} -unsafe impl Sync for DataDynamic where T: Sync {} +unsafe impl Send for DataPtr where T: Send {} +unsafe impl Sync for DataPtr where T: Sync {} -impl DataDynamic { +impl DataPtr { /// Allocates a new data array with the given capacity, if any. /// /// If `T` is zero-sized, or the given capacity is 0, this will not allocate. @@ -566,6 +620,16 @@ impl DataDynamic { unsafe { Self(resolve_ptr(alloc::alloc(layout), layout)) } } + /// Gets a pointer to the data, assuming that it's initialized. + /// + /// # Safety + /// + /// It is up to the caller to guarantee the following: + /// - All of the data that this pointer could point to is valid and initialized + pub unsafe fn ptr_data(&mut self) -> *mut T { + self.0.cast::().as_ptr() + } + /// Gets the raw stored data up to `len` as a mutable `MaybeUninit` slice. /// /// # Safety diff --git a/src/archetype/storage_fixed.rs b/src/archetype/storage_fixed.rs deleted file mode 100644 index c343bd2..0000000 --- a/src/archetype/storage_fixed.rs +++ /dev/null @@ -1,620 +0,0 @@ -use std::cell::{Ref, RefCell, RefMut}; -use std::mem::MaybeUninit; -use std::ptr; - -use seq_macro::seq; - -use crate::archetype::slices::*; -use crate::archetype::slot::{self, Slot, SlotIndex}; -use crate::archetype::view::*; -use crate::entity::{Entity, EntityRaw}; -use crate::index::{DataIndex, MAX_DATA_CAPACITY}; -use crate::traits::{Archetype, EntityKey, StorageCanResolve}; -use crate::util::{debug_checked_assume, num_assert_leq}; -use crate::version::VersionArchetype; - -macro_rules! declare_storage_fixed_n { - ($name:ident, $borrow:ident, $slices:ident, $view:ident, $n:literal) => { - seq!(I in 0..$n { - pub struct $name { - version: VersionArchetype, - len: usize, - free_head: SlotIndex, - slots: DataFixed, // Sparse - // No RefCell here since we never grant mutable access externally - entities: DataFixed, N>, - #(d~I: RefCell>,)* - } - - impl $name - { - #[inline(always)] - pub fn new() -> Self { - // We assume in a lot of places that u32 can trivially convert to usize - num_assert_leq!(std::mem::size_of::(), std::mem::size_of::()); - // Our data indices must be able to fit inside of entity handles - num_assert_leq!(N, MAX_DATA_CAPACITY as usize); - - let mut slots: DataFixed = DataFixed::new(); - // For consistency with the dynamic version. - let raw_data = slots.raw_data(); - let free_head = slot::populate_free_list(DataIndex::zero(), raw_data); - - Self { - version: VersionArchetype::start(), - len: 0, - free_head, - slots, - entities: DataFixed::new(), - #(d~I: RefCell::new(DataFixed::new()),)* - } - } - - #[inline(always)] - pub const fn len(&self) -> usize { - self.len - } - - #[inline(always)] - pub const fn is_empty(&self) -> bool { - self.len == 0 - } - - #[inline(always)] - pub const fn capacity(&self) -> usize { - N - } - - /// The overall version for this data structure. Used for raw indices. - #[inline(always)] - pub const fn version(&self) -> VersionArchetype { - self.version - } - - /// Reserves a slot in the map pointing to the given dense index. - /// Returns an entity handle if successful, or None if we're full. - #[inline(always)] - pub fn try_push(&mut self, data: (#(T~I,)*)) -> Option> { - debug_assert!(self.len <= self.capacity()); - - if self.len >= self.capacity() { - // If we're full, we should also be at the end of the slot free list. - debug_assert!(self.free_head.is_free_list_end()); - - return None; // Out of space - } - - unsafe { - // SAFETY: We will never hit the the free list end if we're below capacity - let slot_index = self.free_head.get_next_free().unwrap_unchecked(); - // SAFETY: We never let self.len be greater than MAX_DATA_CAPACITY. - let dense_index = DataIndex::new_unchecked(self.len as u32); - - // SAFETY: We know that the slot storage is valid up to our capacity. - let slots = self.slots.slice_mut(self.capacity()); - // SAFETY: We know this is not the end of the free list, and we know that - // a free list slot index can never be assigned to an out of bounds value. - let slot = slots.get_unchecked_mut(slot_index.get() as usize); - - // NOTE: Do not change the following order of operations! - debug_assert!(slot.is_free()); - self.free_head = slot.index(); - slot.assign(dense_index); - let entity = Entity::new(slot_index, slot.version()); - let index = self.len; - self.len += 1; - - // SAFETY: We can't overflow because self.len < N. - debug_checked_assume!(index < self.len); - - // SAFETY: We know that index < N and points to an empty cell. - self.entities.write(index, entity); - #(self.d~I.get_mut().write(index, data.I);)* - - Some(entity) - } - } - - /// Removes the given entity from storage if it exists there. - /// Returns the removed entity's components, if any. - /// - /// This effectively destroys the entity by invalidating all handles to it. - /// - /// # Panics - /// - /// This function may panic if a slot's generational version overflows, - /// in order to protect the safety of entity handle lookups. This is an - /// extremely unlikely occurrence in nearly all programs -- it would only - /// happen if the exact same lookup slot was rewritten 4.2 billion times. - #[inline(always)] - pub fn remove(&mut self, entity: Entity) -> Option<(#(T~I,)*)> { - debug_assert!(self.len <= self.capacity()); - - let (slot_index, dense_index) = match self.resolve_slot(entity) { - None => { return None; } - Some(found) => found, - }; - - let result = unsafe { - // SAFETY: These are guaranteed by resolve_slot to be in range. - let slot_index_usize: usize = slot_index.get() as usize; - let dense_index_usize: usize = dense_index.get() as usize; - - debug_assert!(self.len > 0); - debug_assert!(slot_index_usize <= self.capacity()); - debug_assert!(dense_index_usize < self.len); - - let entities = self.entities.slice(self.len); - debug_assert!(entities.len() == self.len); - debug_assert!(entities[dense_index_usize].slot_index() == entity.slot_index()); - debug_assert!(entities[dense_index_usize].version() == entity.version()); - - // SAFETY: We know self.len > 0 because we got Some from resolve_slot. - let last_dense_index = self.len - 1; - // SAFETY: We know the entity slice has a length of self.len. - let last_entity = *entities.get_unchecked(last_dense_index); - // SAFETY: We guarantee that stored entities point to valid slots. - let last_slot_index: usize = last_entity.slot_index().get() as usize; - - // Perform the swap_remove on our data to drop the target entity. - // SAFETY: We guarantee that non-free slots point to valid dense data. - self.entities.swap_remove(dense_index_usize, self.len); - let result = - (#(self.d~I.get_mut().swap_remove(dense_index_usize, self.len),)*); - - // SAFETY: We know that the slot storage is valid up to our capacity. - let slots = self.slots.slice_mut(self.capacity()); - - // NOTE: Order matters here to support the (target == last) case! - // Fix up the slot pointing to the last entity - slots - .get_unchecked_mut(last_slot_index) // SAFETY: See declaration. - .assign(dense_index); - // Return the target slot to the free list - slots - .get_unchecked_mut(slot_index_usize) // SAFETY: See declaration. - .release(self.free_head); - - // Advance this storage's overall version (for add/removes). - self.version = self.version.next(); - - result - }; - - // Update the free list head - self.free_head = SlotIndex::new_free(entity.slot_index()); - self.len -= 1; - - Some(result) - } - - /// Resolves an entity key to an index in the storage data slices. - /// This index is guaranteed to be in bounds and point to valid data. - #[inline(always)] - pub fn resolve(&self, entity: K) -> Option - where - Self: StorageCanResolve - { - >::resolve_for(self, entity) - } - - /// Creates a borrow context to accelerate accessing borrowed data for an entity. - #[inline(always)] - pub fn begin_borrow( - &self, - entity: K - ) -> Option<$borrow> - where - Self: StorageCanResolve - { - if let Some(index) = >::resolve_for(self, entity) { - Some($borrow { index, source: self }) - } else { - None - } - } - - /// Populates a view struct with our stored data for the given entity key. - #[inline(always)] - pub fn get_view_mut<'a, E: $view<'a, A, #(T~I,)*>, K: EntityKey>( - &'a mut self, - entity: K, - ) -> Option - where - Self: StorageCanResolve - { - if let Some(index) = >::resolve_for(self, entity) { - unsafe { - // SAFETY: We guarantee that if we successfully resolve, then index < self.len. - // SAFETY: We guarantee that the storage is valid up to self.len. - Some(( - E::new( - index, - self.entities.slice(self.len).get_unchecked(index), - #(self.d~I.get_mut().slice_mut(self.len).get_unchecked_mut(index),)* - ) - )) - } - } else { - None - } - } - - /// Populates a slice struct with slices to our stored data. - #[inline(always)] - pub fn get_all_slices_mut<'a, S: $slices<'a, A, #(T~I,)*>>(&'a mut self,) -> S - { - unsafe { - debug_checked_assume!(self.len <= N); - // SAFETY: We guarantee that the storage is valid up to self.len. - S::new( - self.entities.slice(self.len), - #(self.d~I.get_mut().slice_mut(self.len),)* - ) - } - } - - /// Gets a read-only slice of our currently stored entity handles. - #[inline(always)] - pub fn get_slice_entities(&self) -> &[Entity] { - unsafe { - debug_checked_assume!(self.len <= N); - // SAFETY: We guarantee that the storage is valid up to self.len. - self.entities.slice(self.len) - } - } - - #( - /// Gets a slice of the given component index. - #[inline(always)] - pub fn get_slice_~I(&mut self) -> &[T~I] { - unsafe { - debug_checked_assume!(self.len <= N); - // SAFETY: We guarantee that the storage is valid up to self.len. - self.d~I.get_mut().slice(self.len) - } - } - - /// Gets a mutable slice of the given component index. - #[inline(always)] - pub fn get_slice_mut_~I(&mut self) -> &mut [T~I] { - unsafe { - debug_checked_assume!(self.len <= N); - // SAFETY: We guarantee that the storage is valid up to self.len. - self.d~I.get_mut().slice_mut(self.len) - } - } - - /// Borrows the slice of the given component index. - #[inline(always)] - pub fn borrow_slice_~I(&self) -> Ref<[T~I]> { - Ref::map(self.d~I.borrow(), |slice| unsafe { - debug_checked_assume!(self.len <= N); - // SAFETY: We guarantee that the storage is valid up to self.len. - slice.slice(self.len) - }) - } - - /// Mutably borrows the slice of the given component index. - #[inline(always)] - pub fn borrow_slice_mut_~I(&self) -> RefMut<[T~I]> { - RefMut::map(self.d~I.borrow_mut(), |slice| unsafe { - debug_checked_assume!(self.len <= N); - // SAFETY: We guarantee that the storage is valid up to self.len. - slice.slice_mut(self.len) - }) - } - )* - - /// Resolves the slot index and data index for a given entity. - /// Both indices are guaranteed to point to valid cells. - #[inline(always)] - fn resolve_slot(&self, entity: Entity) -> Option<(DataIndex, DataIndex)> { - // Nothing to resolve if we have nothing stored - if self.len == 0 { - return None; - } - - // Get the index into the slot array from the entity. - let slot_index = entity.slot_index(); - - unsafe { - let slot_index_usize = slot_index.get() as usize; - - // NOTE: It's a little silly, but we don't actually know if this entity - // was created by this map, so we can't assume internal consistency here. - // We'll just have to take the small hit for bounds checking on the index. - debug_assert!(slot_index_usize < self.capacity(), "invalid entity handle"); - if slot_index_usize >= self.capacity() { - return None; - } - - // SAFETY: We know that the slot storage is valid up to our capacity. - let slots = self.slots.slice(self.capacity()); - // SAFETY: We know slot_index_usize is within bounds due to the panic above. - let slot = slots.get_unchecked(slot_index_usize); - - // NOTE: For similar reasons above, a crossed-wires entity handle from another - // world could miraculously have the correct version while pointing to a freed - // slot. This could cause some wacky memory access, so we need to allow slots - // to be explicitly identified as free or not. Again, this has a small cost. - if (slot.version() != entity.version()) || slot.is_free() { - return None; // Stale entity handle, fail the lookup - } - - // SAFETY: We know that this is not a free slot due to the check above. - let dense_index = slot.index().get_data().unwrap_unchecked(); - - Some((slot_index, dense_index)) - } - } - } - - impl StorageCanResolve> for $name { - #[inline(always)] - fn resolve_for(&self, entity: Entity) -> Option { - // The dense index from resolve_slot is guaranteed to be within bounds. - let (_, dense_index) = match self.resolve_slot(entity) { - None => { return None; } - Some(found) => found, - }; - - let dense_index_usize = dense_index.get() as usize; - - unsafe { - // SAFETY: This is checked when we create and grow. - debug_checked_assume!(self.len <= N); - // SAFETY: This is guaranteed by resolve_slot. - debug_checked_assume!(self.len >= dense_index_usize); - } - - Some(dense_index_usize) - } - } - - impl StorageCanResolve> for $name { - #[inline(always)] - fn resolve_for(&self, raw: EntityRaw) -> Option { - let dense_index = raw.dense_index().get() as usize; - - // We need to guarantee that the resulting index is in bounds. - match (raw.version() == self.version()) && (dense_index < self.len) { - true => Some(dense_index), - false => None, - } - } - } - - impl Drop - for $name - { - #[inline(always)] - fn drop(&mut self) { - // SAFETY: We guarantee that the storage is valid up to self.len. - unsafe { - #(self.d~I.get_mut().drop_to(self.len);)* - // We don't need to drop the other stuff since it's all trivial. - }; - } - } - - impl Default - for $name - { - #[inline(always)] - fn default() -> Self { - $name::new() - } - } - - pub struct $borrow<'a, A: Archetype, #(T~I,)* const N: usize> { - index: usize, - source: &'a $name, - } - - impl<'a, A: Archetype, #(T~I,)* const N: usize> $borrow<'a, A, #(T~I,)* N> { - #[inline(always)] - pub fn index(&self) -> usize { - self.index - } - - #[inline(always)] - pub fn entity(&self) -> &Entity { - unsafe { - // SAFETY: We can only be created with a valid index, and because - // we hold a reference to the source, that reference can't have - // changed in any way that would have made this index invalid. - self.source.get_slice_entities().get_unchecked(self.index) - } - } - - #( - /// Borrows the element of the given component index. - #[inline(always)] - pub fn borrow_~I(&self) -> Ref { - Ref::map(self.source.d~I.borrow(), |slice| unsafe { - debug_assert!(self.index < self.source.len); - // SAFETY: We can only be created with a valid index, and because - // we hold a reference to the source, that reference can't have - // changed in any way that would have made this index invalid. - // SAFETY: We guarantee that the storage is valid up to self.len. - slice.slice(self.source.len).get_unchecked(self.index) - }) - } - - /// Mutably borrows the element of the given component index. - #[inline(always)] - pub fn borrow_mut_~I(&self) -> RefMut { - RefMut::map(self.source.d~I.borrow_mut(), |slice| unsafe { - debug_assert!(self.index < self.source.len); - // SAFETY: We can only be created with a valid index, and because - // we hold a reference to the source, that reference can't have - // changed in any way that would have made this index invalid. - // SAFETY: We guarantee that the storage is valid up to self.len. - slice.slice_mut(self.source.len).get_unchecked_mut(self.index) - }) - } - )* - } - - - impl<'a, A: Archetype, #(T~I,)* const N: usize> Clone for $borrow<'a, A, #(T~I,)* N> { - #[inline(always)] - fn clone(&self) -> Self { - Self { - index: self.index, - source: self.source, - } - } - } - - impl<'a, A: Archetype, #(T~I,)* const N: usize> Copy for $borrow<'a, A, #(T~I,)* N> {} - }); - }; -} - -// Declare storage for up to 16 components. -seq!(N in 1..=16 { - declare_storage_fixed_n!(StorageFixed~N, BorrowFixed~N, Slices~N, View~N, N); -}); - -// Declare additional storage for up to 32 components. -#[cfg(feature = "32_components")] -seq!(N in 17..=32 { - declare_storage_fixed_n!(StorageFixed~N, BorrowFixed~N, Slices~N, View~N, N); -}); - -struct DataFixed(Box<[MaybeUninit; N]>); - -unsafe impl Send for DataFixed where T: Send {} -unsafe impl Sync for DataFixed where T: Sync {} - -impl DataFixed { - /// Creates a new fully uninitialized array. - #[inline(always)] - #[rustfmt::skip] - fn new() -> Self { - let mut v = Vec::with_capacity(N); - // SAFETY: MaybeUninit is always trivially initialized. - unsafe { v.set_len(N) }; - Self( v.try_into().unwrap()) - } - - /// Gets the raw stored data as a mutable `MaybeUninit` slice. - #[inline(always)] - fn raw_data(&mut self) -> &mut [MaybeUninit] { - &mut (*self.0) - } - - /// Writes an element to the given index. - /// - /// # Safety - /// - /// It is up to the caller to guarantee the following: - /// - `index <= N` - /// - The element at `index` is not currently initialized - #[inline(always)] - unsafe fn write(&mut self, index: usize, val: T) { - unsafe { - debug_assert!(index < N); - - self.0.get_unchecked_mut(index).write(val); - } - } - - /// Gets a slice for the range `0..len`. - /// - /// # Safety - /// - /// It is up to the caller to guarantee the following: - /// - All elements in the array in the range `0..len` are initialized - /// - `len <= N` - #[inline(always)] - unsafe fn slice(&self, len: usize) -> &[T] { - unsafe { - debug_assert!(len <= N); - - // SAFETY: Casting a `[MaybeUninit]` to a `[T]` is safe because the caller - // guarantees that this portion of the data is valid and `MaybeUninit` is - // guaranteed to have the same layout as `T`. The pointer obtained is valid - // since it refers to memory owned by `slice` which is a reference and thus - // guaranteed to be valid for reads. - // Ref: https://doc.rust-lang.org/stable/src/core/mem/maybe_uninit.rs.html#972 - &*(self.0.get_unchecked(0..len) as *const [MaybeUninit] as *const [T]) - } - } - - /// Gets a mutable slice for the range `0..len`. - /// - /// # Safety - /// - /// It is up to the caller to guarantee the following: - /// - All elements in the range `0..len` are initialized - /// - `len <= N` - #[inline(always)] - unsafe fn slice_mut(&mut self, len: usize) -> &mut [T] { - unsafe { - debug_assert!(len <= N); - - // SAFETY: Similar to safety notes for `slice`, but we have a mutable reference - // which is also guaranteed to be valid for writes. - // Ref: https://doc.rust-lang.org/stable/src/core/mem/maybe_uninit.rs.html#994 - &mut *(self.0.get_unchecked_mut(0..len) as *mut [MaybeUninit] as *mut [T]) - } - } - - /// Drops the element at `index` and replaces it with the last element in `0..len`. - /// - /// # Safety - /// - /// It is up to the caller to guarantee the following: - /// - All elements in the range `0..len` are initialized - /// - `len <= N` - /// - `len > 0` - /// - `index < N` - /// - `index < len` - #[inline(always)] - unsafe fn swap_remove(&mut self, index: usize, len: usize) -> T { - unsafe { - debug_assert!(len <= N); - debug_assert!(len > 0); - debug_assert!(index < N); - debug_assert!(index < len); - - // SAFETY: The caller is guaranteeing that the element at index, and - // the element at len - 1 are both valid. With this guarantee we can - // safely take the element at index. We then perform a direct pointer - // copy (we can't assume nonoverlapping here!) from the last element - // to the one at index. This moves the data, making the data at index - // initialized to the data at last, and the data at last effectively - // uninitialized (though bitwise identical to the data at index). - let last = len - 1; - let array_ptr = self.0.as_mut_ptr(); - let result = ptr::read(array_ptr.add(index)).assume_init(); - ptr::copy(array_ptr.add(last), array_ptr.add(index), 1); - *array_ptr.add(last) = MaybeUninit::uninit(); // Hint for Miri - result - } - } - - /// Drops all elements in the range `0..len`. - /// - /// # Safety - /// - /// It is up to the caller to guarantee the following: - /// - All elements in the range `0..len` are initialized - /// - `len <= N` - #[inline(always)] - unsafe fn drop_to(&mut self, len: usize) { - unsafe { - debug_assert!(len <= N); - - for i in 0..len { - let i_ptr = self.0.as_mut_ptr().add(i); - // SAFETY: The caller guarantees this element is valid. - ptr::drop_in_place(i_ptr as *mut T); - ptr::write(i_ptr, MaybeUninit::uninit()); // Hint for Miri - } - }; - } -} diff --git a/src/entity.rs b/src/entity.rs index 4d63820..b1be477 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -2,9 +2,9 @@ use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use crate::error::EcsError; -use crate::index::{DataIndex, MAX_DATA_INDEX}; +use crate::index::{TrimmedIndex, MAX_DATA_INDEX}; use crate::traits::{Archetype, EntityKey}; -use crate::version::{VersionArchetype, VersionSlot}; +use crate::version::{ArchetypeVersion, SlotVersion}; // NOTE: While this is extremely unlikely to change, if it does, the proc // macros need to be updated manually with the new type assumptions. @@ -66,7 +66,7 @@ pub struct EntityRaw { #[derive(Clone, Copy, Eq, PartialEq)] pub struct EntityAny { key: u32, // [ slot_index (u24) | archetype_id (u8) ] - version: VersionSlot, + version: SlotVersion, } /// A dynamically typed, versioned raw entity index of some runtime archetype. @@ -78,14 +78,14 @@ pub struct EntityAny { #[derive(Clone, Copy, Eq, PartialEq)] pub struct EntityRawAny { key: u32, // [ dense_index (u24) | archetype_id (u8) ] - version: VersionArchetype, + version: ArchetypeVersion, } impl Entity { #[inline(always)] pub(crate) fn new( - slot_index: DataIndex, //. - version: VersionSlot, + slot_index: TrimmedIndex, //. + version: SlotVersion, ) -> Self { Self { inner: EntityAny::new(slot_index, A::ARCHETYPE_ID, version), @@ -94,12 +94,12 @@ impl Entity { } #[inline(always)] - pub(crate) fn slot_index(&self) -> DataIndex { + pub(crate) fn slot_index(&self) -> TrimmedIndex { self.inner.slot_index() } #[inline(always)] - pub(crate) fn version(&self) -> VersionSlot { + pub(crate) fn version(&self) -> SlotVersion { self.inner.version() } @@ -158,21 +158,22 @@ impl Entity { impl EntityAny { #[inline(always)] pub(crate) fn new( - slot_index: DataIndex, //. + slot_index: TrimmedIndex, //. archetype_id: ArchetypeId, - version: VersionSlot, + version: SlotVersion, ) -> Self { let archetype_id: u32 = archetype_id.into(); - let key = (slot_index.get() << ARCHETYPE_ID_BITS) | archetype_id; + let slot_index: u32 = slot_index.into(); + let key = (slot_index << ARCHETYPE_ID_BITS) | archetype_id; Self { key, version } } #[inline(always)] - pub(crate) fn slot_index(&self) -> DataIndex { + pub(crate) fn slot_index(&self) -> TrimmedIndex { unsafe { // SAFETY: We know the remaining data can fit in a DataIndex debug_assert!(self.key >> ARCHETYPE_ID_BITS <= MAX_DATA_INDEX); - DataIndex::new_unchecked(self.key >> ARCHETYPE_ID_BITS) + TrimmedIndex::new_u32(self.key >> ARCHETYPE_ID_BITS).unwrap_unchecked() } } @@ -185,7 +186,7 @@ impl EntityAny { } #[inline(always)] - pub(crate) const fn version(&self) -> VersionSlot { + pub(crate) const fn version(&self) -> SlotVersion { self.version } @@ -199,8 +200,8 @@ impl EntityAny { impl EntityRaw { #[inline(always)] pub(crate) fn new( - dense_index: DataIndex, //. - version: VersionArchetype, + dense_index: TrimmedIndex, //. + version: ArchetypeVersion, ) -> Self { Self { inner: EntityRawAny::new(dense_index, A::ARCHETYPE_ID, version), @@ -209,12 +210,12 @@ impl EntityRaw { } #[inline(always)] - pub(crate) fn dense_index(&self) -> DataIndex { + pub(crate) fn dense_index(&self) -> TrimmedIndex { self.inner.dense_index() } #[inline(always)] - pub(crate) fn version(&self) -> VersionArchetype { + pub(crate) fn version(&self) -> ArchetypeVersion { self.inner.version() } @@ -273,21 +274,22 @@ impl EntityRaw { impl EntityRawAny { #[inline(always)] pub(crate) fn new( - dense_index: DataIndex, //. + dense_index: TrimmedIndex, //. archetype_id: ArchetypeId, - version: VersionArchetype, + version: ArchetypeVersion, ) -> Self { let archetype_id: u32 = archetype_id.into(); - let key = (dense_index.get() << ARCHETYPE_ID_BITS) | archetype_id; + let dense_index: u32 = dense_index.into(); + let key = (dense_index << ARCHETYPE_ID_BITS) | archetype_id; Self { key, version } } #[inline(always)] - pub(crate) fn dense_index(&self) -> DataIndex { + pub(crate) fn dense_index(&self) -> TrimmedIndex { unsafe { // SAFETY: We know the remaining data can fit in a DataIndex debug_assert!(self.key >> ARCHETYPE_ID_BITS <= MAX_DATA_INDEX); - DataIndex::new_unchecked(self.key >> ARCHETYPE_ID_BITS) + TrimmedIndex::new_u32(self.key >> ARCHETYPE_ID_BITS).unwrap_unchecked() } } @@ -300,7 +302,7 @@ impl EntityRawAny { } #[inline(always)] - pub(crate) const fn version(&self) -> VersionArchetype { + pub(crate) const fn version(&self) -> ArchetypeVersion { self.version } @@ -453,7 +455,7 @@ pub mod __internal { #[doc(hidden)] #[inline(always)] - pub fn new_entity_raw(index: usize, version: VersionArchetype) -> EntityRaw { - EntityRaw::new(DataIndex::new_usize(index).unwrap(), version) + pub fn new_entity_raw(index: usize, version: ArchetypeVersion) -> EntityRaw { + EntityRaw::new(TrimmedIndex::new_usize(index).unwrap(), version) } } diff --git a/src/index.rs b/src/index.rs index 14b55a7..e4bdde6 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,23 +1,21 @@ use crate::entity::ARCHETYPE_ID_BITS; -use crate::util::{debug_checked_assume, num_assert_lt}; +use crate::util::debug_checked_assume; pub(crate) const MAX_DATA_CAPACITY: u32 = 1 << (u32::BITS - ARCHETYPE_ID_BITS); pub(crate) const MAX_DATA_INDEX: u32 = MAX_DATA_CAPACITY - 1; /// A size-checked index that can always fit in an entity and live slot. #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] -pub(crate) struct DataIndex(u32); +pub(crate) struct TrimmedIndex(u32); -impl DataIndex { - /// Creates a `DataIndex` pointing to zero. +impl TrimmedIndex { + /// Creates a `TrimmedIndex` pointing to zero. #[inline(always)] pub(crate) const fn zero() -> Self { - // Better safe than sorry I guess... - num_assert_lt!(0, MAX_DATA_INDEX as usize); Self(0) } - /// Creates a new `DataIndex` if the given index is within bounds. + /// Creates a new `TrimmedIndex` if the given index is within bounds. #[inline(always)] pub(crate) const fn new_u32(index: u32) -> Option { match index < MAX_DATA_CAPACITY { @@ -26,7 +24,7 @@ impl DataIndex { } } - /// Creates a new `DataIndex` if the given index is within bounds. + /// Creates a new `TrimmedIndex` if the given index is within bounds. #[inline(always)] pub(crate) const fn new_usize(index: usize) -> Option { match index < MAX_DATA_CAPACITY as usize { @@ -34,27 +32,20 @@ impl DataIndex { false => None, } } +} - /// Creates a new `DataIndex` without checking any bounds. - /// - /// # Safety - /// - /// The caller must guarantee that `index < MAX_DATA_CAPACITY`. - #[inline(always)] - pub(crate) unsafe fn new_unchecked(index: u32) -> Self { - debug_assert!(index < MAX_DATA_CAPACITY); - Self(index) +impl From for u32 { + fn from(value: TrimmedIndex) -> Self { + // SAFETY: This is verified at creation + unsafe { debug_checked_assume!(value.0 < MAX_DATA_CAPACITY) }; + value.0 } +} - /// Gets the raw value of this `DataIndex`. - /// - /// The result is guaranteed to be less than `MAX_DATA_CAPACITY`. - #[inline(always)] - pub(crate) fn get(self) -> u32 { - unsafe { - // SAFETY: This is verified at creation - debug_checked_assume!(self.0 < MAX_DATA_CAPACITY); - self.0 - } +impl From for usize { + fn from(value: TrimmedIndex) -> Self { + // SAFETY: This is verified at creation + unsafe { debug_checked_assume!(value.0 < MAX_DATA_CAPACITY) }; + value.0.try_into().unwrap() } } diff --git a/src/lib.rs b/src/lib.rs index 93e0a1d..e774024 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,13 +15,6 @@ //! and declared at compile-time, so that adding or removing components from entities at //! runtime isn't currently possible -- hybrid approaches could solve this in the future. //! -//! Archetypes in gecs can be set to contain a fixed or dynamic capacity of entities. If -//! all of the archetypes in your ECS world declaration are set to a fixed capacity, gecs -//! will perform zero allocations after startup. This guarantees that your ECS world will -//! adhere to a known and predictable memory overhead for constrained environments (e.g. -//! servers on cloud instances). Attempting to add an entity to a full archetype can -//! either report failure or panic depending on the method you call to do so. -//! //! The goals for gecs are (in descending priority order): //! - Fast iteration and find queries //! - Fast entity creation and destruction @@ -49,8 +42,8 @@ //! //! ecs_world! { //! // Declare two archetypes, ArchFoo and ArchBar. -//! ecs_archetype!(ArchFoo, 100, CompA, CompB); // Fixed capacity of 100 entities. -//! ecs_archetype!(ArchBar, dyn, CompA, CompC); // Dynamic (dyn) entity capacity. +//! ecs_archetype!(ArchFoo, CompA, CompB); +//! ecs_archetype!(ArchBar, CompA, CompC); //! } //! //! fn main() { @@ -140,20 +133,12 @@ mod macros { /// ## ecs_archetype! /// /// ```ignore - /// ecs_archetype!(Name, capacity, Component, ...); + /// ecs_archetype!(Name, Component, ...); /// ``` /// The `ecs_archetype!` inner pseudo-macro is used for declaring an archetype in an ECS /// world. It takes the following arguments: /// /// - `Name`: The name (in PascalCase) of the archetype Rust type. - /// - `capacity`: The capacity of the archetype, specified in one of the following ways: - /// - A constant expression (e.g. `200` or `config::ARCH_CAPACITY + 4`). This will - /// create a fixed-size archetype that can contain at most that number of entities. - /// - The `dyn` keyword can be used to create a dynamically-sized archetype. This can - /// grow to accommodate up to `16,777,216` entities. To initialize an ECS world's - /// dynamic archetype with a pre-allocated capacity, use the `with_capacity()` - /// function at world creation. This function will be automatically generated - /// with a named capacity argument for each dynamic archetype in that world. /// - `Component, ...`: One or more component types to include in this archetype. Because /// generated archetypes are `pub` with `pub` members, all components must be `pub` too. /// @@ -177,15 +162,12 @@ mod macros { /// #[cfg(feature = "some_feature")] // CompC only exists if "some_feature" is enabled. /// pub struct CompC(pub u32); /// - /// const BAR_CAPACITY: usize = 30; - /// /// ecs_world! { /// ecs_name!(MyWorld); // Set the type name of this ECS structure to MyWorld. /// - /// // Declare an archetype called ArchFoo with capacity 100 and two components. + /// // Declare an archetype called ArchFoo with two components. /// ecs_archetype!( /// ArchFoo, - /// 100, /// CompA, // Note: Type paths are not currently supported for components. /// CompB, /// ); @@ -194,7 +176,6 @@ mod macros { /// #[cfg(feature = "some_feature")] /// ecs_archetype!( /// ArchBar, - /// BAR_CAPACITY, // Constants may also be used for archetype capacity. /// CompA, /// CompC, /// ); @@ -202,7 +183,6 @@ mod macros { /// #[archetype_id(6)] /// ecs_archetype!( /// ArchBaz, - /// dyn, // Use the dyn keyword for a dynamically-sized archetype. /// CompA, /// CompB, /// #[cfg(feature = "some_feature")] @@ -211,10 +191,12 @@ mod macros { /// } /// /// fn main() { - /// // Create a new world. Because ArchBaz is the only dynamic archetype, we only need to - /// // set one capacity in world creation (the parameter is named capacity_arch_baz). The - /// // other fixed-size archetypes will always be created sized to their full capacity. - /// let mut world = MyWorld::with_capacity(30); + /// // Create a new world. You can use new() or pass a structure to specify capacities. + /// let mut world = MyWorld::with_capacity(MyWorldCapacity { + /// arch_foo: 5, + /// #[cfg(feature = "some_feature")] arch_bar: 5, + /// arch_baz: 5, + /// }); /// /// // Create an ArchFoo entity in the world and unwrap the Option>. /// // Alternatively, we could use .create(), which will panic if the archetype is full. @@ -261,7 +243,7 @@ mod macros { /// use super::super::*; /// /// ecs_world! { - /// ecs_archetype!(ArchFoo, 10, CompA, CompB); + /// ecs_archetype!(ArchFoo, CompA, CompB); /// } /// } /// } @@ -317,14 +299,12 @@ mod macros { /// ecs_world! { /// ecs_archetype!( /// ArchFoo, - /// 5, /// CompA, // = 0 /// CompC, // = 1 /// ); /// /// ecs_archetype!( /// ArchBar, - /// 5, /// #[component_id(6)] /// CompA, // = 6 /// CompB, // = 7 (Implicit) @@ -333,7 +313,6 @@ mod macros { /// /// ecs_archetype!( /// ArchBaz, - /// 5, /// CompA, // = 0 (Implicit) /// CompB, // = 1 (Implicit) /// #[component_id(200)] @@ -422,8 +401,8 @@ mod macros { /// pub struct CompC(pub u32); /// /// ecs_world! { - /// ecs_archetype!(ArchFoo, 100, CompA, CompB); - /// ecs_archetype!(ArchBar, 100, CompA, CompC); + /// ecs_archetype!(ArchFoo, CompA, CompB); + /// ecs_archetype!(ArchBar, CompA, CompC); /// } /// /// // If you need to use a non-mut reference, see the ecs_find_borrow! macro. @@ -479,7 +458,7 @@ mod macros { /// pub struct Parent(pub Option>); /// /// ecs_world! { - /// ecs_archetype!(ArchFoo, 100, CompA, CompB, Parent); + /// ecs_archetype!(ArchFoo, CompA, CompB, Parent); /// } /// /// fn main() { @@ -556,8 +535,8 @@ mod macros { /// pub struct CompC(pub u32); /// /// ecs_world! { - /// ecs_archetype!(ArchFoo, 100, CompA, CompB); - /// ecs_archetype!(ArchBar, 100, CompA, CompC); + /// ecs_archetype!(ArchFoo, CompA, CompB); + /// ecs_archetype!(ArchBar, CompA, CompC); /// } /// /// fn main() { @@ -647,8 +626,8 @@ mod macros { /// pub struct CompC(pub u32); /// /// ecs_world! { - /// ecs_archetype!(ArchFoo, 100, CompA, CompB); - /// ecs_archetype!(ArchBar, 100, CompA, CompC); + /// ecs_archetype!(ArchFoo, CompA, CompB); + /// ecs_archetype!(ArchBar, CompA, CompC); /// } /// /// fn main() { @@ -715,8 +694,8 @@ mod macros { /// pub struct CompC(pub u32); /// /// ecs_world! { -/// ecs_archetype!(ArchFoo, 5, CompA, CompB); -/// ecs_archetype!(ArchBar, 5, CompA, CompC); +/// ecs_archetype!(ArchFoo, CompA, CompB); +/// ecs_archetype!(ArchBar, CompA, CompC); /// } /// /// fn main() { @@ -786,11 +765,10 @@ pub mod __internal { pub use entity::__internal::*; - pub use version::{VersionArchetype, VersionSlot}; + pub use version::{ArchetypeVersion, SlotVersion}; pub use archetype::slices::*; - pub use archetype::storage_dynamic::*; - pub use archetype::storage_fixed::*; + pub use archetype::storage::*; pub use archetype::view::*; pub use iter::{EcsStepDestroy, EcsStep}; @@ -801,7 +779,4 @@ pub mod __internal { pub use traits::{World, WorldHas}; pub use traits::{Archetype, ArchetypeHas}; pub use traits::{View, ViewHas}; - - - pub use itertools::izip as izip; } diff --git a/src/traits.rs b/src/traits.rs index cda7d64..5b8c31f 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -181,8 +181,8 @@ where /// pub struct CompA(pub u32); /// /// ecs_world! { -/// // Declare archetype ArchFoo with capacity 100 and one component: CompA -/// ecs_archetype!(ArchFoo, 100, CompA); +/// // Declare archetype ArchFoo with one component: CompA +/// ecs_archetype!(ArchFoo, CompA); /// } /// /// fn get_arch_foo_len(world: &mut W) -> usize @@ -224,8 +224,8 @@ pub trait WorldHas: World { /// pub struct CompA(pub u32); /// /// ecs_world! { -/// // Declare archetype ArchFoo with capacity 100 and one component: CompA -/// ecs_archetype!(ArchFoo, 100, CompA); +/// // Declare archetype ArchFoo with one component: CompA +/// ecs_archetype!(ArchFoo, CompA); /// } /// /// fn sum_comp_a(archetype: &mut A) -> u32 @@ -300,9 +300,9 @@ pub trait View { /// pub struct CompB(pub u32); /// /// ecs_world! { -/// // Declare archetype ArchFoo with capacity 100 and one component: CompA -/// ecs_archetype!(ArchFoo, 100, CompA, CompB); -/// ecs_archetype!(ArchBar, 100, CompA, CompB); +/// // Declare archetype ArchFoo with one component: CompA +/// ecs_archetype!(ArchFoo, CompA, CompB); +/// ecs_archetype!(ArchBar, CompA, CompB); /// } /// /// fn generic_access_comp_a(view: &mut V) -> u32 diff --git a/src/version.rs b/src/version.rs index f9d3466..39e7c5c 100644 --- a/src/version.rs +++ b/src/version.rs @@ -5,17 +5,17 @@ const VERSION_START: u32 = 1; #[repr(transparent)] #[derive(Clone, Copy, Eq, PartialEq)] -pub struct VersionSlot { +pub struct SlotVersion { version: NonZeroU32, } #[repr(transparent)] #[derive(Clone, Copy, Eq, PartialEq)] -pub struct VersionArchetype { +pub struct ArchetypeVersion { version: NonZeroU32, } -impl VersionSlot { +impl SlotVersion { #[inline(always)] pub(crate) fn start() -> Self { // This is a slightly messy hack to create a NonZeroU32 constant. @@ -33,8 +33,8 @@ impl VersionSlot { } #[inline(always)] - pub(crate) fn next(&self) -> VersionSlot { - VersionSlot { + pub(crate) fn next(&self) -> SlotVersion { + SlotVersion { #[cfg(feature = "wrapping_slot_version")] version: NonZeroU32::new(u32::max(self.version.get().wrapping_add(1), VERSION_START)) .unwrap(), @@ -47,7 +47,7 @@ impl VersionSlot { } } -impl VersionArchetype { +impl ArchetypeVersion { #[inline(always)] pub(crate) fn start() -> Self { // This is a slightly messy hack to create a NonZeroU32 constant. @@ -65,8 +65,8 @@ impl VersionArchetype { } #[inline(always)] - pub(crate) fn next(&self) -> VersionArchetype { - VersionArchetype { + pub(crate) fn next(&self) -> ArchetypeVersion { + ArchetypeVersion { #[cfg(feature = "wrapping_entity_raw_version")] version: NonZeroU32::new(u32::max(self.version.get().wrapping_add(1), VERSION_START)) .unwrap(), diff --git a/tests/test_entity_wild.rs b/tests/test_entity_wild.rs index d1a7e29..a23fa1b 100644 --- a/tests/test_entity_wild.rs +++ b/tests/test_entity_wild.rs @@ -11,14 +11,12 @@ ecs_world! { #[archetype_id(3)] ecs_archetype!( ArchFoo, - 5, CompA, CompB, ); ecs_archetype!( ArchBar, - 5, CompA, CompC, ); diff --git a/tests/test_iter.rs b/tests/test_iter.rs new file mode 100644 index 0000000..aa90c32 --- /dev/null +++ b/tests/test_iter.rs @@ -0,0 +1,44 @@ +use gecs::prelude::*; + +pub struct CompA(pub u32); +pub struct CompZ; // ZST + +ecs_world! { + ecs_archetype!( + ArchFoo, + CompA, + CompZ, + ); +} + +#[test] +#[rustfmt::skip] +pub fn test_single_iter() { + let mut world = EcsWorld::default(); + + world.arch_foo.create((CompA(0), CompZ,)); + world.arch_foo.create((CompA(1), CompZ,)); + world.arch_foo.create((CompA(2), CompZ,)); + world.arch_foo.create((CompA(3), CompZ,)); + world.arch_foo.create((CompA(4), CompZ,)); + + let mut vec = Vec::new(); + + for (_, a, _) in world.arch_foo.iter() { + vec.push(a.0); + } + + assert_eq!(vec, vec![0, 1, 2, 3, 4]); + + vec.clear(); + + for (_, a, _) in world.arch_foo.iter_mut() { + a.0 += 1; + } + + for (_, a, _) in world.arch_foo.iter() { + vec.push(a.0); + } + + assert_eq!(vec, vec![1, 2, 3, 4, 5]); +} diff --git a/tests/test_iter_destroy.rs b/tests/test_iter_destroy.rs index e2dbe42..36f74c0 100644 --- a/tests/test_iter_destroy.rs +++ b/tests/test_iter_destroy.rs @@ -11,14 +11,12 @@ ecs_world! { #[archetype_id(3)] ecs_archetype!( ArchFoo, - 5, CompA, CompB, ); ecs_archetype!( ArchBar, - 5, CompA, CompC, ); diff --git a/tests/test_multi.rs b/tests/test_multi.rs index 998d96d..d4544ee 100644 --- a/tests/test_multi.rs +++ b/tests/test_multi.rs @@ -1,7 +1,5 @@ use gecs::prelude::*; -const TEST_CAPACITY: usize = 5; - #[derive(Debug, PartialEq)] pub struct CompA(pub u32); #[derive(Debug, PartialEq)] @@ -13,14 +11,12 @@ ecs_world! { #[archetype_id(3)] ecs_archetype!( ArchFoo, - TEST_CAPACITY, CompA, CompB, ); ecs_archetype!( ArchBar, - 5, // Test both inputs CompA, CompC, ); @@ -36,7 +32,10 @@ pub fn test_archetype_id() { #[test] #[rustfmt::skip] pub fn test_multi_create_direct() { - let mut world = EcsWorld::default(); + let mut world = EcsWorld::with_capacity(EcsWorldCapacity { + arch_foo: 5, + arch_bar: 3, + }); world.archetype_mut::().create((CompA(0), CompB(10))); world.archetype_mut::().create((CompA(1), CompB(11))); diff --git a/tests/test_one_of.rs b/tests/test_one_of.rs index e19defb..a741a7a 100644 --- a/tests/test_one_of.rs +++ b/tests/test_one_of.rs @@ -11,14 +11,12 @@ ecs_world! { #[archetype_id(3)] ecs_archetype!( ArchFoo, - 5, CompA, CompB, ); ecs_archetype!( ArchBar, - 5, CompA, CompC, ); diff --git a/tests/test_raw.rs b/tests/test_raw.rs index 8c57e4d..620bad6 100644 --- a/tests/test_raw.rs +++ b/tests/test_raw.rs @@ -11,14 +11,12 @@ ecs_world! { #[archetype_id(3)] ecs_archetype!( ArchFoo, - dyn, CompA, CompB, ); ecs_archetype!( ArchBar, - dyn, CompA, CompC, ); diff --git a/tests/test_single.rs b/tests/test_single.rs index 4325bea..eb2e6d0 100644 --- a/tests/test_single.rs +++ b/tests/test_single.rs @@ -1,16 +1,11 @@ use gecs::prelude::*; -mod inner { - pub(crate) const TEST_CAPACITY: usize = 4; -} - pub struct CompA(pub u32); pub struct CompZ; // ZST ecs_world! { ecs_archetype!( ArchFoo, - inner::TEST_CAPACITY + 1, // Should be 5 total for tests CompA, CompZ, ); @@ -34,8 +29,6 @@ pub fn test_single_create() { world.arch_foo.create((CompA(4), CompZ,)); assert_eq!(world.arch_foo.len(), 5); - - assert!(world.arch_foo.try_create((CompA(6), CompZ,)).is_none()); } #[test] diff --git a/tests/test_single_dyn.rs b/tests/test_single_dyn.rs index d1c7997..2c62b89 100644 --- a/tests/test_single_dyn.rs +++ b/tests/test_single_dyn.rs @@ -6,7 +6,6 @@ pub struct CompZ; // ZST ecs_world! { ecs_archetype!( ArchFoo, - dyn, CompA, CompZ, ); @@ -29,7 +28,7 @@ pub fn test_single_dyn_create() { #[test] #[rustfmt::skip] pub fn test_single_dyn_create_with_capacity_zero() { - let mut world = EcsWorld::with_capacity(0); + let mut world = EcsWorld::with_capacity(EcsWorldCapacity { arch_foo: 0 }); world.arch_foo.create((CompA(0), CompZ,)); world.arch_foo.create((CompA(1), CompZ,)); @@ -43,7 +42,7 @@ pub fn test_single_dyn_create_with_capacity_zero() { #[test] #[rustfmt::skip] pub fn test_single_dyn_create_with_capacity_all() { - let mut world = EcsWorld::with_capacity(5); + let mut world = EcsWorld::with_capacity(EcsWorldCapacity { arch_foo: 5 }); world.arch_foo.create((CompA(0), CompZ,)); world.arch_foo.create((CompA(1), CompZ,)); @@ -111,7 +110,7 @@ pub fn test_single_dyn_entity() { #[test] #[rustfmt::skip] pub fn test_single_dyn_entity_with_capacity() { - let mut world = EcsWorld::with_capacity(5); + let mut world = EcsWorld::with_capacity(EcsWorldCapacity { arch_foo: 5 }); let entity_0 = world.arch_foo.create((CompA(0), CompZ,)); let entity_1 = world.arch_foo.create((CompA(1), CompZ,)); diff --git a/tests/test_util.rs b/tests/test_util.rs index 92f97d9..f1d391a 100644 --- a/tests/test_util.rs +++ b/tests/test_util.rs @@ -7,14 +7,12 @@ pub struct CompC; ecs_world! { ecs_archetype!( ArchFoo, - 5, CompA, // = 0 CompC, // = 1 ); ecs_archetype!( ArchBar, - 5, #[component_id(6)] CompA, // = 6 CompB, // = 7 (Implicit) @@ -23,7 +21,6 @@ ecs_world! { ecs_archetype!( ArchBaz, - 5, CompA, // = 0 (Implicit) CompB, // = 1 (Implicit) #[component_id(200)]