ecs: add not filter, improve the code for inserting components into entity, bundle cleanup and improvements

This commit is contained in:
SeanOMik 2024-04-10 22:26:49 -04:00
parent 01a74ab9a6
commit 4a0d003181
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
8 changed files with 269 additions and 67 deletions

View File

@ -1,6 +1,6 @@
use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::DerefMut};
use crate::{world::ArchetypeEntityId, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick, Entity};
use crate::{bundle::Bundle, component_info::ComponentInfo, world::ArchetypeEntityId, DynTypeId, Entities, Entity, Record, Tick};
#[derive(Clone)]
pub struct ComponentColumn {
@ -295,21 +295,32 @@ impl Archetype {
self.capacity = new_cap;
}
debug_assert_eq!(self.entity_ids.len(), self.entities.len(), "Somehow the Archetype's entity storage got unsynced");
self.ensure_synced();
let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64);
self.entity_ids.insert(entity, entity_index);
self.entities.push(entity);
bundle.take(|data, type_id, _size| {
let col = self.get_column_mut(type_id).unwrap();
unsafe { col.insert_entity(entity_index.0 as usize, data, *tick); }
bundle.take(|data, type_id, info| {
self.put_component_at(tick, data, type_id, info.layout().size(), entity_index.0 as _);
});
entity_index
}
/// Removes an entity from the Archetype and frees its components. Returns the entity record
/// that took its place in the component column.
pub(crate) fn put_component_at(&mut self, tick: &Tick, ptr: NonNull<u8>, type_id: DynTypeId, size: usize, index: usize) {
let _ = size;
let col = self.get_column_mut(type_id).unwrap();
//unsafe { col.set_at(index, ptr, *tick) };
unsafe { col.insert_entity(index, ptr, *tick); }
}
/// Removes an entity from the Archetype and frees its components.
///
/// Inside the component columns, the entities are swap-removed. Meaning that the last
/// entity in the column is moved in the position of the entity that was removed.
/// If there was an entity that was swapped, this function returns the entity, and its
/// new index in the archetype that was put in place of the removed entity.
pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> {
let entity_index = self.entity_ids.remove(&entity)
.expect("The entity is not in this Archetype!");
@ -389,6 +400,11 @@ impl Archetype {
self.capacity = new_capacity;
}
/// Attempts to find the column storing components of `type_id`
pub fn get_column_at(&self, idx: usize) -> Option<&ComponentColumn> {
self.columns.get(idx)
}
/// Attempts to find the column storing components of `type_id`
pub fn get_column<I: Into<DynTypeId>>(&self, type_id: I) -> Option<&ComponentColumn> {
let type_id = type_id.into();
@ -436,7 +452,7 @@ impl Archetype {
/// The entity IS NOT removed from the old archetype. You must manually call [`Archetype::remove_entity`](crate::Archetype).
/// It was done this way because I had some borrow check issues when writing [`World::insert`](crate::World)
/// related to borrowing mutably from self more than once.
/* pub fn move_into<B>(&self, into_arch: &mut Archetype, entity: Entity, new_components: B)
/* pub fn move_into<B>(&mut self, entities: &mut Entities, tick: &Tick, into_arch: &mut Archetype, entity: Entity, new_components: B)
where
B: Bundle
{
@ -446,25 +462,40 @@ impl Archetype {
// move the existing components into the new archetype
for col in self.columns.iter() {
let into_col = into_arch.get_column_mut(col.info.type_id).unwrap();
let into_col = into_arch.get_column_mut(col.info.type_id()).unwrap();
// copy from the old column into the new column, then remove it from the old one
unsafe {
let ptr = col.borrow_ptr();
let ptr = NonNull::new_unchecked(ptr.as_ptr()
.add(new_index.0 as usize * col.info.layout.size));
into_col.set_at(new_index.0 as _, ptr);
.add(new_index.0 as usize * col.info.layout().size()));
into_col.set_at(new_index.0 as _, ptr, *tick);
//into_col.set_at(new_index.0 as _, ptr);
}
}
// now move the new components into the new archetype
new_components.take(|data, type_id, _size| {
let col = into_arch.get_column_mut(type_id).unwrap();
unsafe { col.set_at(new_index.0 as _, data); }
unsafe { col.set_at(new_index.0 as _, data, *tick); }
col.len += 1;
});
//self.remove_entity(entity);
if let Some((en, new_idx)) = self.remove_entity(entity, tick) {
let moved_rec = Record {
id: self.id,
index: new_idx,
};
entities.insert_entity_record(en, moved_rec);
}
let new_rec = Record {
id: into_arch.id,
index: new_index,
};
entities.insert_entity_record(entity, new_rec);
into_arch.ensure_synced();
self.ensure_synced();
} */
/// Returns a borrow to the map used to find the column indices of the entity.
@ -700,7 +731,7 @@ mod tests {
#[test]
fn dynamic_archetype() {
let layout = Layout::new::<u32>();
let info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", layout);
let info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), layout);
let infos = vec![info.clone()];
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), infos);

View File

@ -1,4 +1,4 @@
use std::{ptr::NonNull, mem::size_of, alloc::Layout};
use std::{ptr::NonNull, alloc::Layout};
use crate::{component::Component, component_info::ComponentInfo, DynTypeId};
@ -11,7 +11,7 @@ pub trait Bundle {
/// Take the bundle by calling the closure with pointers to each component, its type and size.
/// The closure is expected to take ownership of the pointer.
fn take(self, f: impl FnMut(NonNull<u8>, DynTypeId, usize));
fn take(self, f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo));
/// Returns a boolean indicating if this Bundle is dynamic. See [`DynamicBundle`]
fn is_dynamic(&self) -> bool;
@ -26,8 +26,8 @@ impl Bundle for () {
vec![ComponentInfo::new::<()>()]
}
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, usize)) {
f(NonNull::from(&self).cast(), DynTypeId::of::<()>(), size_of::<()>());
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
f(NonNull::from(&self).cast(), DynTypeId::of::<()>(), ComponentInfo::new::<()>());
}
fn is_dynamic(&self) -> bool {
@ -44,8 +44,8 @@ impl<C: Component> Bundle for C {
vec![ComponentInfo::new::<C>()]
}
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, usize)) {
f(NonNull::from(&self).cast(), DynTypeId::of::<C>(), size_of::<C>());
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
f(NonNull::from(&self).cast(), DynTypeId::of::<C>(), ComponentInfo::new::<C>());
// this must be done to avoid calling drop on heap memory that the component
// may manage. So something like a Vec, or HashMap, etc.
@ -70,12 +70,12 @@ macro_rules! impl_bundle_tuple {
vec![$(ComponentInfo::new::<$name>()),+]
}
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, usize)) {
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
// these names wont follow rust convention, but its a macro so deal with it
let ($($name,)+) = self;
$(
f(NonNull::from(&$name).cast(), DynTypeId::of::<$name>(), size_of::<$name>());
f(NonNull::from(&$name).cast(), DynTypeId::of::<$name>(), ComponentInfo::new::<$name>());
// this must be done to avoid calling drop on heap memory that the component
// may manage. So something like a Vec, or HashMap, etc.
std::mem::forget($name);
@ -136,8 +136,6 @@ impl DynamicBundle {
where
C: Component
{
let info = ComponentInfo::new::<C>();
// an owned pointer must be created from the provided component since comp would drop
// out of scope and the data would become invalid
let ptr = unsafe {
@ -147,16 +145,42 @@ impl DynamicBundle {
let alloc_ptr = NonNull::new_unchecked(std::alloc::alloc(layout)).cast::<C>();
std::ptr::copy_nonoverlapping(data.as_ptr(), alloc_ptr.as_ptr(), 1);
// this must be done to avoid calling drop on heap memory that the component
// may manage. So something like a Vec, or HashMap, etc.
std::mem::forget(comp);
alloc_ptr.cast()
};
let info = ComponentInfo::new::<C>();
self.bundle.push((ptr, info));
}
/// Push an unknown type to the bundle
/// Push an unknown type to the end of the bundle.
pub fn push_unknown(&mut self, data: NonNull<u8>, info: ComponentInfo) {
self.bundle.push((data, info));
}
/// Push a bundle to the end of this dynamic bundle.
pub fn push_bundle<B>(&mut self, bundle: B)
where
B: Bundle
{
bundle.take(|ptr, _, info| {
// unfortunately the components in the bundle must be copied since there is no guarantee that
// `bundle` lasts for as long as the `DynamicBundle`. If the data wasn't copied, the pointers
// could be invalid later.
let p = unsafe {
let alloc_ptr = NonNull::new_unchecked(std::alloc::alloc(info.layout()));
std::ptr::copy_nonoverlapping(ptr.as_ptr(), alloc_ptr.as_ptr(), info.layout().size());
alloc_ptr
};
self.push_unknown(p, info);
});
}
}
impl Bundle for DynamicBundle {
@ -168,9 +192,9 @@ impl Bundle for DynamicBundle {
self.bundle.iter().map(|b| b.1).collect()
}
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, usize)) {
for (data, info) in self.bundle.iter() {
f(*data, info.type_id(), info.layout().size());
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
for (data, info) in self.bundle.into_iter() {
f(data, info.type_id(), info);
}
}

View File

@ -67,7 +67,7 @@ impl ComponentInfo {
}
/// Create ComponentInfo from a type that is not known to rust
pub fn new_unknown<D>(type_id: D, name: &str, layout: Layout) -> Self
pub fn new_unknown<D>(name: Option<String>, type_id: D, layout: Layout) -> Self
where
D: Into<DynTypeId>,
{

View File

@ -11,6 +11,16 @@ pub struct Entity {
pub(crate) generation: u64,
}
impl Entity {
pub fn id(&self) -> EntityId {
self.id
}
pub fn generation(&self) -> u64 {
self.generation
}
}
pub struct Entities {
pub(crate) arch_index: HashMap<EntityId, Record>,
dead: VecDeque<Entity>,

View File

@ -111,7 +111,7 @@ mod tests {
#[test]
fn single_dynamic_view() {
let comp_layout = Layout::new::<u32>();
let comp_info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", comp_layout);
let comp_info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), comp_layout);
let mut dynamic_bundle = DynamicBundle::default();
let comp = 50u32;

View File

@ -3,3 +3,6 @@ pub use has_component::*;
pub mod or;
pub use or::*;
pub mod not;
pub use not::*;

View File

@ -0,0 +1,45 @@
use std::marker::PhantomData;
use crate::{query::{AsQuery, Query}, Archetype, World};
#[derive(Default)]
pub struct Not<Q: Query> {
q: Q,
_marker: PhantomData<Q>
}
impl<Q: Query> Copy for Not<Q> {}
impl<Q: Query> Clone for Not<Q> {
fn clone(&self) -> Self {
Self {
q: self.q.clone(),
_marker: self._marker.clone()
}
}
}
impl<Q: Query> Query for Not<Q> {
type Item<'a> = ();
type Fetch<'a> = ();
fn new() -> Self {
Not {
q: Q::new(),
_marker: PhantomData
}
}
fn can_visit_archetype(&self, archetype: &Archetype) -> bool {
!self.q.can_visit_archetype(archetype)
}
unsafe fn fetch<'a>(&self, _world: &'a World, _: &'a Archetype, _: crate::Tick) -> Self::Fetch<'a> {
()
}
}
impl<Q: Query> AsQuery for Not<Q> {
type Query = Self;
}

View File

@ -2,7 +2,7 @@ use std::{any::TypeId, collections::HashMap, ptr::NonNull};
use atomic_refcell::{AtomicRef, AtomicRefMut};
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewState, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker};
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker};
/// The id of the entity for the Archetype.
///
@ -144,37 +144,32 @@ impl World {
let current_arch = self.archetypes.get(&record.id).unwrap();
let current_arch_len = current_arch.len();
let mut col_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id()).collect();
let orig_col = col_types.clone();
col_types.extend(bundle.type_ids());
// contains the type ids for the old component columns + the ids for the new components
let mut combined_column_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id()).collect();
combined_column_types.extend(bundle.type_ids());
let mut col_infos: Vec<ComponentInfo> = current_arch.columns.iter().map(|c| c.info).collect();
col_infos.extend(bundle.info());
// contains the ComponentInfo for the old component columns + the info for the new components
let mut combined_column_infos: Vec<ComponentInfo> = current_arch.columns.iter().map(|c| c.info).collect();
combined_column_infos.extend(bundle.info());
let col_ptrs: Vec<(NonNull<u8>, ComponentInfo)> = current_arch.columns.iter()
// pointers only for the old columns
let old_columns: Vec<(NonNull<u8>, ComponentInfo)> = current_arch.columns.iter()
.map(|c| unsafe { (NonNull::new_unchecked(c.borrow_ptr().as_ptr()), c.info) })
.collect();
if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&col_types)) {
let res_index = arch.reserve_one(entity);
for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) {
if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&combined_column_types)) {
let mut dbun = DynamicBundle::new();
// move old entity components into new archetype columns
for (col_ptr, col_info) in old_columns.into_iter() {
unsafe {
let ptr = NonNull::new_unchecked(col_ptr.as_ptr()
.add(record.index.0 as usize * col_info.layout().size()));
let col = arch.get_column_mut(col_type).unwrap();
// set_at is used since the entity was reserved
col.set_at(res_index.0 as _, ptr, tick);
dbun.push_unknown(ptr, col_info);
}
}
dbun.push_bundle(bundle);
bundle.take(|data, type_id, _size| {
let col = arch.get_column_mut(type_id).unwrap();
// set_at is used since the entity was reserved
unsafe { col.set_at(res_index.0 as _, data, tick); }
});
arch.entity_ids.insert(entity, res_index);
let res_index = arch.add_entity(entity, dbun, &tick);
arch.ensure_synced();
let new_record = Record {
@ -187,25 +182,24 @@ impl World {
// if this entity is the only entity for this archetype, add more columns to it
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
current_arch.extend(&tick, vec![bundle]);
return;
}
let new_arch_id = self.next_archetype_id.increment();
let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos);
let entity_arch_id = archetype.add_entity(entity, bundle, &tick);
let mut archetype = Archetype::from_bundle_info(new_arch_id, combined_column_infos);
// move the old components into the new archetype
for (column_ptr, column_info) in col_ptrs.into_iter() {
let mut dbun = DynamicBundle::new();
for (column_ptr, column_info) in old_columns.into_iter() {
unsafe {
// ptr of component for the entity
let comp_ptr = NonNull::new_unchecked(column_ptr.as_ptr()
.add(record.index.0 as usize * column_info.layout().size()));
let col = archetype.get_column_mut(column_info.type_id()).unwrap();
col.insert_entity(entity_arch_id.0 as _, comp_ptr, tick);
dbun.push_unknown(comp_ptr, column_info);
}
}
dbun.push_bundle(bundle);
let entity_arch_id = archetype.add_entity(entity, dbun, &tick);
self.archetypes.insert(new_arch_id, archetype);
@ -219,12 +213,103 @@ impl World {
}
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
if current_arch.len() > 1 {
current_arch.remove_entity(entity, &tick);
} else if current_arch.len() == 1 {
// The old archetype will only be removed if there was another archetype that would
// work for the entity's components, and the old archetype only had a single entity.
self.archetypes.remove(&record.id).unwrap();
if let Some((en, enar)) = current_arch.remove_entity(entity, &tick) {
let rec = Record {
id: current_arch.id(),
index: enar
};
self.entities.insert_entity_record(en, rec);
}
current_arch.ensure_synced();
}
/// A method used for debugging implementation details of the ECS.
///
/// Here's an example of the output:
/// ```
/// Entities
/// 1 in archetype 0 at 0
/// 0 in archetype 1 at 0
/// 2 in archetype 0 at 1
/// 3 in archetype 2 at 0
/// Arch 1 -- 1 entities
/// Col 175564825027445222460146453544114453753
/// 0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 78 86 0 0
/// Col 162279302565774655543278578489329315472
/// 0: 0 0 32 65 0 0 32 65 0 0 32 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
/// Col 24291284537013640759061027938209843602
/// 0: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
/// Arch 2 -- 1 entities
/// Col 175564825027445222460146453544114453753
/// 0: 0 0 0 0 0 0 0 0 0 0 0 0 237 127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 78 86 0 0
/// Col 162279302565774655543278578489329315472
/// 0: 0 0 76 66 0 0 170 66 0 0 136 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
/// Col 142862377085187052737282554588643015580
/// 0: 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
/// Arch 0 -- 2 entities
/// Col 175564825027445222460146453544114453753
/// 0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
/// 1: 0 0 0 0 0 0 0 0 0 0 0 0 237 127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 78 86 0 0
/// Col 162279302565774655543278578489329315472
/// 0: 0 0 112 65 0 0 112 65 0 0 112 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
/// 1: 0 0 27 67 0 0 184 65 0 0 192 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
/// Col 142862377085187052737282554588643015580
/// 0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
/// 1: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
/// Col 24291284537013640759061027938209843602
/// 0: 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
/// 1: 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
/// Arch 3 -- 0 entities
/// Col 175564825027445222460146453544114453753
/// Col 162279302565774655543278578489329315472
/// ```
///
/// This output prints all Entity ids, the archetype they're in, and the index that they're
/// in inside of the archetype. Additionally, the archetypes are printing, including their
/// columns type ids, and the contents of the type ids. This output can be used to debug
/// the contents of entities inside the archetypes.
///
/// Below is a template of the output:
/// ```
/// Entities
/// %ENTITY_ID% in archetype %ARCHETYPE_ID% at %INDEX%
/// Arch ID -- %ARCHETYPE_LEN% entities
/// %FOR EACH COL%
/// Col COLUMN_COMPONENT_TYPE_ID
/// %FOR EACH ENTITY%
/// %ENTITY_INDEX%: %COMPONENT_BYTES%
/// ```
/// If the template above doesn't help you in understanding the output, read the source code
/// of the function. The source code is pretty simple.
pub fn debug_print_world(&self) {
println!("Entities");
for (en, rec) in &self.entities.arch_index {
println!(" {} in archetype {} at {}", en.0, rec.id.0, rec.index.0);
}
for arch in self.archetypes.values() {
println!("Arch {} -- {} entities", arch.id().0, arch.len());
for col in &arch.columns {
// no clue if doing this is stable, but this is a debug function so :shrug:
let tyid: u128 = unsafe { std::mem::transmute(col.info.type_id().as_rust()) };
println!(" Col {}", tyid);
for en in 0..col.len {
// get the ptr starting at the component
let p = col.borrow_ptr();
let p = unsafe { p.as_ptr().add(en * col.info.layout().size()) };
print!(" {}: ", en);
// print each byte of the component
for i in 0..col.info.layout().size() {
let d = unsafe { *p.add(i) };
print!("{} ", d);
}
println!();
}
}
}
}
@ -364,6 +449,10 @@ impl World {
self.resources.get(&TypeId::of::<T>())
.map(|d| unsafe { NonNull::new_unchecked(d.data.as_ptr() as *mut T) })
}
pub fn archetype_count(&self) -> usize {
self.archetypes.len()
}
}
// TODO: Ensure that all non-send resources are only accessible on the main thread.