diff --git a/codegen/src/zip.rs b/codegen/src/zip.rs index 3304a29a90..5023002ae5 100644 --- a/codegen/src/zip.rs +++ b/codegen/src/zip.rs @@ -37,42 +37,42 @@ pub(crate) fn imp(input: TokenStream) -> Result { } impl<__Arch: #crate_name::Archetype, #(#field_ty),*> - #crate_name::system::access::IntoZip<__Arch> + #crate_name::system::IntoZip<__Arch> for #ident<#(#field_ty,)*> where - #(#field_ty: #crate_name::system::access::IntoZip<__Arch>,)* + #(#field_ty: #crate_name::system::IntoZip<__Arch>,)* { - type IntoZip = #ident<#(<#field_ty as #crate_name::system::access::IntoZip<__Arch>>::IntoZip,)*>; + type IntoZip = #ident<#(<#field_ty as #crate_name::system::IntoZip<__Arch>>::IntoZip,)*>; fn into_zip(self) -> Self::IntoZip { let Self { #(#field_ident,)* } = self; #ident { #( - #field_ident: <#field_ty as #crate_name::system::access::IntoZip<__Arch>>::into_zip(#field_ident), + #field_ident: <#field_ty as #crate_name::system::IntoZip<__Arch>>::into_zip(#field_ident), )* } } } impl<__Arch: #crate_name::Archetype, #(#field_ty),*> - #crate_name::system::access::Zip<__Arch> + #crate_name::system::Zip<__Arch> for #ident<#(#field_ty,)*> where - #(#field_ty: #crate_name::system::access::Zip<__Arch>,)* + #(#field_ty: #crate_name::system::Zip<__Arch>,)* { fn split(&mut self, offset: __Arch::RawEntity) -> Self { let Self { #(#field_ident,)* } = self; #ident { #( - #field_ident: <#field_ty as #crate_name::system::access::Zip<__Arch>>::split(#field_ident, offset), + #field_ident: <#field_ty as #crate_name::system::Zip<__Arch>>::split(#field_ident, offset), )* } } type Item = #ident< - #(<#field_ty as #crate_name::system::access::Zip<__Arch>>::Item,)* + #(<#field_ty as #crate_name::system::Zip<__Arch>>::Item,)* >; fn get>(self, __dynec_entity: E) -> Self::Item { let Self { #(#field_ident,)* } = self; let __dynec_entity = #crate_name::entity::TempRef::<__Arch>::new(__dynec_entity.id()); #ident { #( - #field_ident: <#field_ty as #crate_name::system::access::Zip<__Arch>>::get( + #field_ident: <#field_ty as #crate_name::system::Zip<__Arch>>::get( #field_ident, __dynec_entity, ), @@ -81,18 +81,18 @@ pub(crate) fn imp(input: TokenStream) -> Result { } impl<__Arch: #crate_name::Archetype, #(#field_ty),*> - #crate_name::system::access::ZipChunked<__Arch> + #crate_name::system::ZipChunked<__Arch> for #ident<#(#field_ty,)*> where - #(#field_ty: #crate_name::system::access::ZipChunked<__Arch>,)* + #(#field_ty: #crate_name::system::ZipChunked<__Arch>,)* { type Chunk = #ident< - #(<#field_ty as #crate_name::system::access::ZipChunked<__Arch>>::Chunk,)* + #(<#field_ty as #crate_name::system::ZipChunked<__Arch>>::Chunk,)* >; fn get_chunk(self, __dynec_chunk: #crate_name::entity::TempRefChunk<__Arch>) -> Self::Chunk { let Self { #(#field_ident,)* } = self; #ident { #( - #field_ident: <#field_ty as #crate_name::system::access::ZipChunked<__Arch>>::get_chunk( + #field_ident: <#field_ty as #crate_name::system::ZipChunked<__Arch>>::get_chunk( #field_ident, __dynec_chunk, ), diff --git a/src/system.rs b/src/system.rs index 33571247d7..9ad75160ef 100644 --- a/src/system.rs +++ b/src/system.rs @@ -20,13 +20,16 @@ pub use crate::world::rw::isotope::write::partial::WriteIsotopePartial; pub use crate::world::rw::simple::{ReadSimple, WriteSimple}; pub mod access; -pub use access::{Isotope as AccessIsotope, Single as AccessSingle, Try}; +pub use access::{Isotope as AccessIsotope, Single as AccessSingle}; + +pub mod iter; +pub use iter::{EntityIterator, IntoZip, Try, Zip, ZipChunked}; pub mod partition; pub use partition::{EntityCreationPartition, Partition}; -mod entity; -pub use entity::{EntityCreator, EntityDeleter, EntityIterator}; +mod offline_buffer; +pub use offline_buffer::{EntityCreator, EntityDeleter}; pub mod spec; #[doc(inline)] diff --git a/src/system/access.rs b/src/system/access.rs index cf4ee3b2cc..9231b127d1 100644 --- a/src/system/access.rs +++ b/src/system/access.rs @@ -6,6 +6,3 @@ pub use single::Single; pub mod isotope; pub use isotope::Isotope; pub(crate) use isotope::{PartialStorageMap, StorageMap, StorageMapMut}; - -mod iter; -pub use iter::{IntoZip, Try, Zip, ZipChunked}; diff --git a/src/system/access/isotope.rs b/src/system/access/isotope.rs index d51f4340ae..c7f3537511 100644 --- a/src/system/access/isotope.rs +++ b/src/system/access/isotope.rs @@ -320,3 +320,6 @@ where storages.map(|storage| AccessSingle::new(storage)) } } + +#[cfg(test)] +mod tests; diff --git a/src/world/tests/isotope.rs b/src/system/access/isotope/tests.rs similarity index 95% rename from src/world/tests/isotope.rs rename to src/system/access/isotope/tests.rs index c6651bec38..e2e68e7e50 100644 --- a/src/world/tests/isotope.rs +++ b/src/system/access/isotope/tests.rs @@ -4,16 +4,8 @@ use crate::test_util::*; use crate::{system, system_test, tracer, world}; fn isotope_discrim_read_test_system( - mut iso1: system::AccessIsotope< - TestArch, - IsoNoInit, - impl system::access::StorageMap, - >, - mut iso2: system::AccessIsotope< - TestArch, - IsoWithInit, - impl system::access::StorageMap, - >, + mut iso1: impl system::access::isotope::Get, + mut iso2: impl system::access::isotope::Get, initials: &InitialEntities, ) { let ent = initials.strong.as_ref().expect("initials.strong is None"); diff --git a/src/system/access/single.rs b/src/system/access/single.rs index 15d1ed0e34..9b776b81a5 100644 --- a/src/system/access/single.rs +++ b/src/system/access/single.rs @@ -389,3 +389,6 @@ where .map(|(entity, data)| (entity::TempRefChunk::new(entity, entity.add(data.len())), data)) } } + +#[cfg(test)] +mod tests; diff --git a/src/world/tests/simple.rs b/src/system/access/single/tests.rs similarity index 100% rename from src/world/tests/simple.rs rename to src/system/access/single/tests.rs diff --git a/src/system/entity.rs b/src/system/entity.rs deleted file mode 100644 index bf98b47e2f..0000000000 --- a/src/system/entity.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::cell::RefCell; -use std::marker::PhantomData; -use std::mem; - -use super::access; -use crate::entity::{self, ealloc, Raw as _}; -use crate::world::offline; -use crate::{comp, Archetype}; - -/// Allows creating entities of an archetype. -pub struct EntityCreator<'t, A: Archetype> { - buffer: &'t RefCell<&'t mut offline::BufferShard>, - ealloc: ealloc::BorrowedShard<'t, A>, -} - -impl<'t, A: Archetype> EntityCreator<'t, A> { - /// Constructs an entity creator. - pub fn new( - buffer: &'t RefCell<&'t mut offline::BufferShard>, - ealloc: ealloc::BorrowedShard<'t, A>, - ) -> Self { - Self { buffer, ealloc } - } - - /// Queues to create an entity. - pub fn create(&mut self, comps: comp::Map) -> entity::Entity { - self.with_hint(comps, Default::default()) - } - - /// Queues to create an entity with hint. - pub fn with_hint( - &mut self, - comps: comp::Map, - hint: ::AllocHint, - ) -> entity::Entity { - let mut buffer = self.buffer.borrow_mut(); - let ealloc = &mut *self.ealloc; - buffer.create_entity_with_hint_and_shard(comps, &mut *ealloc, hint) - } -} - -/// Allows deleting entities of an archetype. -pub struct EntityDeleter<'t, A: Archetype> { - buffer: &'t RefCell<&'t mut offline::BufferShard>, - _ph: PhantomData, -} - -impl<'t, A: Archetype> EntityDeleter<'t, A> { - /// Constructs an entity deleter from a macro. - pub fn new(buffer: &'t RefCell<&'t mut offline::BufferShard>) -> Self { - Self { buffer, _ph: PhantomData } - } - - /// Queues to mark an entity for deletion. - pub fn queue>(&mut self, entity: E) { - let mut buffer = self.buffer.borrow_mut(); - buffer.delete_entity::(entity); - } -} - -/// Allows iterating all entities of an archetype. -pub struct EntityIterator { - ealloc: ealloc::Snapshot, -} - -impl EntityIterator { - /// Constructs an instance of [`EntityIterator`] that reads from the given allocator. - /// - /// Although this function accepts an allocator shard, - /// it actually reads the global buffer shared between shards, - /// which is independent of the changes in the current shard. - /// Hence, the iterator describe the state after the previous tick completes, - /// which does not include newly initialized entities - /// and includes those queued for deletion. - /// This behavior is reasonable, because newly initialized entities should not be accessed at all, - /// and those queued for deletion may have a finalizer or - /// be given a finalizer when running later systems, - /// so those queued for deletion are still included. - /// - /// This function is typically called from the code generated by - /// [`#[system]`](macro@crate::system). - pub fn new(ealloc: ealloc::Snapshot) -> Self { Self { ealloc } } - - /// Iterates over all entity IDs in this archetype. - pub fn entities(&self) -> impl Iterator> { - self.ealloc - .iter_allocated_chunks() - .flat_map(::range) - .map(entity::TempRef::new) - } - - /// Iterates over all contiguous chunks of entity IDs. - pub fn chunks(&self) -> impl Iterator> + '_ { - self.ealloc - .iter_allocated_chunks() - .map(|range| entity::TempRefChunk::new(range.start, range.end)) - } - - /// Iterates over all entities, yielding the components requested. - pub fn entities_with>( - &self, - zip: IntoZ, - ) -> impl Iterator, >::Item)> { - let mut zip = ZipIter(zip.into_zip(), PhantomData); - self.ealloc - .iter_allocated_chunks() - .flat_map(::range) - .map(move |entity| (entity::TempRef::new(entity), zip.take_serial(entity))) - } - - /// Iterates over all entities, yielding the components requested in contiguous chunks. - pub fn chunks_with>( - &self, - zip: IntoZ, - ) -> impl Iterator, >::Chunk)> - where - IntoZ::IntoZip: access::ZipChunked, - { - let mut zip = ZipIter(zip.into_zip(), PhantomData); - self.ealloc.iter_allocated_chunks().map(move |chunk| { - ( - entity::TempRefChunk::new(chunk.start, chunk.end), - zip.take_serial_chunk(chunk.start, chunk.end), - ) - }) - } -} - -struct ZipIter>(Z, PhantomData); - -impl> ZipIter { - fn take_serial(&mut self, entity: A::RawEntity) -> Z::Item { - let right = self.0.split(entity.add(1)); - let left = mem::replace(&mut self.0, right); - left.get(entity::TempRef::new(entity)) - } -} - -impl> ZipIter { - fn take_serial_chunk(&mut self, start: A::RawEntity, end: A::RawEntity) -> Z::Chunk { - let right = self.0.split(end.add(1)); - let left = mem::replace(&mut self.0, right); - left.get_chunk(entity::TempRefChunk::new(start, end)) - } -} diff --git a/src/system/access/iter.rs b/src/system/iter.rs similarity index 65% rename from src/system/access/iter.rs rename to src/system/iter.rs index f9504cacce..c2c0cb1d98 100644 --- a/src/system/access/iter.rs +++ b/src/system/iter.rs @@ -1,9 +1,104 @@ +//! Iterate over entities of an archetype. +//! +//! While individual accessors also provide functions like +//! [`AccessSingle::iter`](access::Single::iter), +//! functions in [`EntityIterator`] use the entity indices +//! from the entity allocator snapshot directly, +//! enabling better performance with chunk partitioning. + use std::marker::PhantomData; -use std::{any, ops}; +use std::{any, mem, ops}; +use crate::entity::{ealloc, Raw as _}; use crate::system::access; use crate::{comp, entity, storage, util, Archetype, Storage}; +/// Allows iterating all entities of an archetype. +pub struct EntityIterator { + ealloc: ealloc::Snapshot, +} + +impl EntityIterator { + /// Constructs an instance of [`EntityIterator`] that reads from the given allocator. + /// + /// Although this function accepts an allocator shard, + /// it actually reads the global buffer shared between shards, + /// which is independent of the changes in the current shard. + /// Hence, the iterator describe the state after the previous tick completes, + /// which does not include newly initialized entities + /// and includes those queued for deletion. + /// This behavior is reasonable, because newly initialized entities should not be accessed at all, + /// and those queued for deletion may have a finalizer or + /// be given a finalizer when running later systems, + /// so those queued for deletion are still included. + /// + /// This function is typically called from the code generated by + /// [`#[system]`](macro@crate::system). + pub fn new(ealloc: ealloc::Snapshot) -> Self { Self { ealloc } } + + /// Iterates over all entity IDs in this archetype. + pub fn entities(&self) -> impl Iterator> { + self.ealloc + .iter_allocated_chunks() + .flat_map(::range) + .map(entity::TempRef::new) + } + + /// Iterates over all contiguous chunks of entity IDs. + pub fn chunks(&self) -> impl Iterator> + '_ { + self.ealloc + .iter_allocated_chunks() + .map(|range| entity::TempRefChunk::new(range.start, range.end)) + } + + /// Iterates over all entities, yielding the components requested. + pub fn entities_with>( + &self, + zip: IntoZ, + ) -> impl Iterator, >::Item)> { + let mut zip = ZipIter(zip.into_zip(), PhantomData); + self.ealloc + .iter_allocated_chunks() + .flat_map(::range) + .map(move |entity| (entity::TempRef::new(entity), zip.take_serial(entity))) + } + + /// Iterates over all entities, yielding the components requested in contiguous chunks. + pub fn chunks_with>( + &self, + zip: IntoZ, + ) -> impl Iterator, >::Chunk)> + where + IntoZ::IntoZip: ZipChunked, + { + let mut zip = ZipIter(zip.into_zip(), PhantomData); + self.ealloc.iter_allocated_chunks().map(move |chunk| { + ( + entity::TempRefChunk::new(chunk.start, chunk.end), + zip.take_serial_chunk(chunk.start, chunk.end), + ) + }) + } +} + +struct ZipIter>(Z, PhantomData); + +impl> ZipIter { + fn take_serial(&mut self, entity: A::RawEntity) -> Z::Item { + let right = self.0.split(entity.add(1)); + let left = mem::replace(&mut self.0, right); + left.get(entity::TempRef::new(entity)) + } +} + +impl> ZipIter { + fn take_serial_chunk(&mut self, start: A::RawEntity, end: A::RawEntity) -> Z::Chunk { + let right = self.0.split(end.add(1)); + let left = mem::replace(&mut self.0, right); + left.get_chunk(entity::TempRefChunk::new(start, end)) + } +} + /// Multiple single accessors zipped together, /// to be used with [`EntityIterator::entities_with`](crate::system::EntityIterator::entities_with). /// @@ -52,9 +147,11 @@ pub trait IntoZip { fn into_zip(self) -> Self::IntoZip; } -/// Either returns Option or unwraps it. +/// Determines how to resolve the case of a missing Result. pub trait MissingResln { + /// The return type of the resolution. type Result; + /// Resolves an optional value. fn must_or_try(option: Option) -> Self::Result; } @@ -106,6 +203,7 @@ where fn into_zip(self) -> Self::IntoZip { Read { accessor: self, _ph: PhantomData } } } +/// [`IntoZip::IntoZip`] for read-only accessors. pub struct Read<'t, A, C, StorageRef, Resln> { accessor: &'t access::Single, _ph: PhantomData, @@ -181,6 +279,7 @@ where fn into_zip(self) -> Self::IntoZip { Write { accessor: self.as_partition(), _ph: PhantomData } } } +/// [`IntoZip::IntoZip`] for mutable accessors. pub struct Write<'t, A, C, PartitionT, Resln> { accessor: access::Single, _ph: PhantomData<(&'t mut C, Resln)>, @@ -218,3 +317,6 @@ where } mod tuple_impls; + +#[cfg(test)] +mod tests; diff --git a/src/world/tests/entity_iter.rs b/src/system/iter/tests.rs similarity index 100% rename from src/world/tests/entity_iter.rs rename to src/system/iter/tests.rs diff --git a/src/system/access/iter/tuple_impls.rs b/src/system/iter/tuple_impls.rs similarity index 100% rename from src/system/access/iter/tuple_impls.rs rename to src/system/iter/tuple_impls.rs diff --git a/src/system/offline_buffer.rs b/src/system/offline_buffer.rs new file mode 100644 index 0000000000..ba3496399f --- /dev/null +++ b/src/system/offline_buffer.rs @@ -0,0 +1,60 @@ +use std::cell::RefCell; +use std::marker::PhantomData; + +use crate::entity::{self, ealloc}; +use crate::world::offline; +use crate::{comp, Archetype}; + +/// Allows creating entities of an archetype. +pub struct EntityCreator<'t, A: Archetype> { + buffer: &'t RefCell<&'t mut offline::BufferShard>, + ealloc: ealloc::BorrowedShard<'t, A>, +} + +impl<'t, A: Archetype> EntityCreator<'t, A> { + /// Constructs an entity creator. + pub fn new( + buffer: &'t RefCell<&'t mut offline::BufferShard>, + ealloc: ealloc::BorrowedShard<'t, A>, + ) -> Self { + Self { buffer, ealloc } + } + + /// Queues to create an entity. + pub fn create(&mut self, comps: comp::Map) -> entity::Entity { + self.with_hint(comps, Default::default()) + } + + /// Queues to create an entity with hint. + pub fn with_hint( + &mut self, + comps: comp::Map, + hint: ::AllocHint, + ) -> entity::Entity { + let mut buffer = self.buffer.borrow_mut(); + let ealloc = &mut *self.ealloc; + buffer.create_entity_with_hint_and_shard(comps, &mut *ealloc, hint) + } +} + +/// Allows deleting entities of an archetype. +pub struct EntityDeleter<'t, A: Archetype> { + buffer: &'t RefCell<&'t mut offline::BufferShard>, + _ph: PhantomData, +} + +impl<'t, A: Archetype> EntityDeleter<'t, A> { + /// Constructs an entity deleter from a macro. + pub fn new(buffer: &'t RefCell<&'t mut offline::BufferShard>) -> Self { + Self { buffer, _ph: PhantomData } + } + + /// Queues to mark an entity for deletion. + pub fn queue>(&mut self, entity: E) { + let mut buffer = self.buffer.borrow_mut(); + buffer.delete_entity::(entity); + } +} + +#[cfg(test)] +mod tests; diff --git a/src/world/tests/offline_buffer.rs b/src/system/offline_buffer/tests.rs similarity index 98% rename from src/world/tests/offline_buffer.rs rename to src/system/offline_buffer/tests.rs index 81589e8654..bfdd359ae1 100644 --- a/src/world/tests/offline_buffer.rs +++ b/src/system/offline_buffer/tests.rs @@ -143,7 +143,7 @@ fn test_entity_delete() { all(not(debug_assertions), feature = "release-entity-rc"), ), should_panic = "Detected dangling strong reference to entity dynec::test_util::TestArch#1 in \ - system dynec::world::tests::offline_buffer::test_system. All strong \ + system dynec::system::offline_buffer::tests::test_system. All strong \ references to an entity must be dropped before queuing for deletion and \ removing all finalizers." )] @@ -187,7 +187,7 @@ fn test_entity_delete_send_system_leak() { all(not(debug_assertions), feature = "release-entity-rc"), ), should_panic = "Detected dangling strong reference to entity dynec::test_util::TestArch#1 in \ - system dynec::world::tests::offline_buffer::test_system. All strong \ + system dynec::system::offline_buffer::tests::test_system. All strong \ references to an entity must be dropped before queuing for deletion and \ removing all finalizers." )] diff --git a/src/world/tests.rs b/src/world/tests.rs index e189442f77..b03d623f59 100644 --- a/src/world/tests.rs +++ b/src/world/tests.rs @@ -1,24 +1,4 @@ #![allow(clippy::ptr_arg)] -use crate::system; -use crate::test_util::*; - -#[system(dynec_as(crate))] -fn common_test_system( - _comp3: system::ReadSimple, - _comp4: system::WriteSimple, - _comp5: system::ReadSimple, - _comp6: system::ReadSimple, - #[dynec(isotope(discrim = [TestDiscrim1(11), TestDiscrim1(17)]))] - _iso1: system::ReadIsotopePartial, - #[dynec(global)] _aggregator: &mut Aggregator, - #[dynec(global)] _initials: &InitialEntities, -) { -} - mod dependencies; -mod entity_iter; mod globals; -mod isotope; -mod offline_buffer; -mod simple; diff --git a/src/world/tests/dependencies.rs b/src/world/tests/dependencies.rs index 591438d4d2..90e6239b8d 100644 --- a/src/world/tests/dependencies.rs +++ b/src/world/tests/dependencies.rs @@ -1,8 +1,20 @@ //! Tests autoinit dependencies. -use super::common_test_system; -use crate::system_test; use crate::test_util::*; +use crate::{system, system_test}; + +#[system(dynec_as(crate))] +fn common_test_system( + _comp3: system::ReadSimple, + _comp4: system::WriteSimple, + _comp5: system::ReadSimple, + _comp6: system::ReadSimple, + #[dynec(isotope(discrim = [TestDiscrim1(11), TestDiscrim1(17)]))] + _iso1: system::ReadIsotopePartial, + #[dynec(global)] _aggregator: &mut Aggregator, + #[dynec(global)] _initials: &InitialEntities, +) { +} #[test] fn test_dependencies_successful() {