From 8c8e7dfd7d2e3db84c0c94881eb017d61a2a0ff7 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 14 Dec 2023 00:25:09 -0500 Subject: [PATCH] Implement inserting components into entities, ViewOne --- lyra-ecs/src/archetype.rs | 139 ++++++++++++++++++++++++++++++++++--- lyra-ecs/src/bundle.rs | 31 +++++++-- lyra-ecs/src/query/view.rs | 34 ++++++++- lyra-ecs/src/world.rs | 118 +++++++++++++++++++++++++++++-- 4 files changed, 297 insertions(+), 25 deletions(-) diff --git a/lyra-ecs/src/archetype.rs b/lyra-ecs/src/archetype.rs index 48e3ded..fc43fe4 100644 --- a/lyra-ecs/src/archetype.rs +++ b/lyra-ecs/src/archetype.rs @@ -1,11 +1,11 @@ -use std::{any::TypeId, ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::{DerefMut, Deref}}; +use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::{DerefMut, Deref}}; use crate::{world::{Entity, ArchetypeEntityId}, bundle::Bundle, component_info::ComponentInfo, DynTypeId}; pub struct ComponentColumn { - data: RefCell>, - len: usize, - capacity: usize, + pub data: RefCell>, + pub len: usize, + pub capacity: usize, pub info: ComponentInfo, } @@ -73,7 +73,11 @@ impl ComponentColumn { let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * self.info.layout.size)); ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), self.info.layout.size); - self.len += 1; + + unsafe { + let layout = self.info.layout.into_layout_unchecked(); + std::alloc::dealloc(comp_src.as_ptr(), layout); + } } /// Get a component at an entities index. @@ -239,8 +243,9 @@ impl Archetype { self.entities.insert(entity, ArchetypeEntityId(entity_index as u64)); bundle.take(|data, type_id, _size| { - let col = self.columns.iter_mut().find(|c| c.info.type_id == type_id).unwrap(); + let col = self.get_column_mut(type_id).unwrap(); unsafe { col.set_at(entity_index, data); } + col.len += 1; }); ArchetypeEntityId(entity_index as u64) @@ -262,7 +267,7 @@ impl Archetype { if let Some((_, aid)) = removed_entity { assert!(res as u64 == aid.0); // make sure we removed the same entity } else { - let replaced_entity = self.entities.iter().find(|(e, a)| a.0 == res as u64) + let replaced_entity = self.entities.iter().find(|(_, a)| a.0 == res as u64) .map(|(e, _a)| *e).expect("Failure to find entity for moved component!"); removed_entity = Some((replaced_entity, ArchetypeEntityId(res as u64))); } @@ -270,14 +275,20 @@ impl Archetype { assert!(removed_entity.is_none()); } } + + // safe from the .expect at the start of this method. + self.entities.remove(&entity).unwrap(); // now change the ArchetypeEntityId to be the index that the moved entity was moved into. - removed_entity.map(|(e, a)| (e, entity_index)) + removed_entity.map(|(e, _a)| (e, entity_index)) + } /// Returns a boolean indicating whether this archetype can store the TypeIds given - pub(crate) fn is_archetype_for(&self, types: Vec) -> bool { - self.columns.iter().all(|c| types.contains(&c.info.type_id)) + pub(crate) fn is_archetype_for(&self, types: &Vec) -> bool { + if types.len() == self.columns.len() { + self.columns.iter().all(|c| types.contains(&c.info.type_id)) + } else { false } } /// Returns a boolean indicating whether this archetype has a column for `comp_type` @@ -312,6 +323,69 @@ impl Archetype { self.capacity = new_capacity; } + + pub fn get_column(&self, type_id: DynTypeId) -> Option<&ComponentColumn> { + self.columns.iter().find(|c| c.info.type_id == type_id) + } + + pub fn get_column_mut(&mut self, type_id: DynTypeId) -> Option<&mut ComponentColumn> { + self.columns.iter_mut().find(|c| c.info.type_id == type_id) + } + + /// Reserves a slot in the columns for an entity and returns the index of that reserved spot + pub fn reserve_one(&mut self, entity: Entity) -> ArchetypeEntityId { + if self.capacity == self.entities.len() { + let new_cap = self.capacity * 2; + self.grow_columns(new_cap); + self.capacity = new_cap; + } + + let entity_index = self.entities.len(); + self.entities.insert(entity, ArchetypeEntityId(entity_index as u64)); + + for col in self.columns.iter_mut() { + col.len += 1; + } + + ArchetypeEntityId(entity_index as u64) + } + + /// Moves the entity from this archetype into another one. + /// + /// # Safety + /// The entity IS NOT removed from the old archetype. You must manually call [`Archetype::remove_entity`]. + /// It was done this way because I had some borrow check issues when writing [`World::insert`] + /// related to borrowing mutably from self more than once. + pub fn move_into(&self, into_arch: &mut Archetype, entity: Entity, new_components: B) + where + B: Bundle + { + let new_index = into_arch.reserve_one(entity); + + //let infos: Vec = into_arch.columns.iter().map(|c| c.info).collect(); + + // 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(); + + // 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); + } + } + + // 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); } + col.len += 1; + }); + + //self.remove_entity(entity); + } } #[cfg(test)] @@ -320,7 +394,7 @@ mod tests { use rand::Rng; - use crate::{tests::{Vec2, Vec3}, world::{Entity, EntityId}, bundle::{Bundle, self}, ComponentInfo, MemoryLayout, DynTypeId, DynamicBundle}; + use crate::{ArchetypeId, tests::{Vec2, Vec3}, world::{Entity, EntityId}, bundle::Bundle, ComponentInfo, MemoryLayout, DynTypeId, DynamicBundle}; use super::Archetype; @@ -520,4 +594,47 @@ mod tests { assert_eq!(unsafe { *ptr.cast::().as_ref() }, comp); assert_eq!(col.info, info); } + + #[test] + fn move_archetypes() { + let bundles = vec![Vec2::rand(), ]; + + let mut info = (bundles[0],).info(); + let mut a = Archetype::from_bundle_info(ArchetypeId(0), info.clone()); + + let entity = Entity { + id: EntityId(0), + generation: 0 + }; + + a.add_entity(entity, (bundles[0],)); + + info.extend((Vec3::new(0.0, 0.0, 0.0),).info().into_iter()); + let mut new_a = Archetype::from_bundle_info(ArchetypeId(1), info); + + let inserting_vec3 = Vec3::rand(); + let new_bundle = (inserting_vec3,); + a.move_into(&mut new_a, entity, new_bundle); + + let new_index = new_a.entities.get(&entity).unwrap(); + + // Check Vec3 + { + let new_col = new_a.get_column(DynTypeId::of::()).unwrap(); + let from_col = unsafe { new_col.get::(new_index.0 as _) }; + println!("expected {:?}, got {:?}", inserting_vec3, *from_col); + assert_eq!(*from_col, inserting_vec3); + } + + // Check Vec2 + { + let new_col = new_a.get_column(DynTypeId::of::()).unwrap(); + let from_col = unsafe { new_col.get::(new_index.0 as _) }; + println!("expected {:?}, got {:?}", bundles[0], *from_col); + assert_eq!(*from_col, bundles[0]); + } + + assert!(a.entities.contains_key(&entity)); + assert!(a.remove_entity(entity).is_none()); // No entity would take its place + } } \ No newline at end of file diff --git a/lyra-ecs/src/bundle.rs b/lyra-ecs/src/bundle.rs index aaa9495..735a48e 100644 --- a/lyra-ecs/src/bundle.rs +++ b/lyra-ecs/src/bundle.rs @@ -1,4 +1,4 @@ -use std::{any::{TypeId, Any}, ptr::NonNull, mem::size_of}; +use std::{any::{TypeId, Any}, ptr::NonNull, mem::size_of, alloc::Layout}; use crate::{component::Component, component_info::ComponentInfo, DynTypeId}; @@ -37,7 +37,6 @@ macro_rules! impl_bundle_tuple { impl<$($name: Component),+> Bundle for ($($name,)+) { fn type_ids(&self) -> Vec { // these names wont follow rust convention, but its a macro so deal with it - let ($($name),+) = self; vec![$(DynTypeId::of::<$name>()),+] } @@ -72,26 +71,46 @@ impl_bundle_tuple! { C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14 impl_bundle_tuple! { C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15 } impl_bundle_tuple! { C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16 } -/// A bundle of components -#[derive(Default)] +/// A bundle of a dynamic number of components. The types of the components may not be known to Rust. +/// +/// # Safety +/// Do not drop this without inserting it into an archetype. It WILL cause a memory leak +#[derive(Default, Clone)] pub struct DynamicBundle { bundle: Vec<(NonNull, ComponentInfo)>, } +// TODO: When a bundle is dropped without being inserted into an archetype, it WILL cause a memory leak. Find a way around that. +// maybe it can be done with Rc, or Weak, or a mixture of both. impl DynamicBundle { pub fn new() -> Self { Self::default() } + pub fn len(&self) -> usize { + self.bundle.len() + } + /// Push a type known to rust to this bundle pub fn push(&mut self, comp: C) where C: Component { let info = ComponentInfo::new::(); - let data = NonNull::from(&comp).cast::(); + + // 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 { + let data = NonNull::from(&comp); - self.bundle.push((data, info)); + let layout = Layout::new::(); + let alloc_ptr = NonNull::new_unchecked(std::alloc::alloc(layout)).cast::(); + std::ptr::copy_nonoverlapping(data.as_ptr(), alloc_ptr.as_ptr(), 1); + + alloc_ptr.cast() + }; + + self.bundle.push((ptr, info)); } /// Push an unknown type to the bundle diff --git a/lyra-ecs/src/query/view.rs b/lyra-ecs/src/query/view.rs index 1dabfbd..c319d08 100644 --- a/lyra-ecs/src/query/view.rs +++ b/lyra-ecs/src/query/view.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use crate::{archetype::{Archetype, ArchetypeId}, world::{ArchetypeEntityId, World}}; +use crate::{archetype::{Archetype, ArchetypeId}, world::{ArchetypeEntityId, World}, EntityId}; use super::{Query, Fetch}; @@ -105,4 +105,36 @@ where } } } +} + +pub struct ViewOne<'a, Q: Query> { + world: &'a World, + entity: EntityId, + query: Q, +} + +impl<'a, Q: Query> ViewOne<'a, Q> { + pub fn new(world: &'a World, entity: EntityId, query: Q) -> Self { + Self { + world, + entity, + query + } + } + + pub fn get(&self) -> Option> { + if let Some(record) = self.world.entity_index.get(&self.entity) { + let arch = self.world.archetypes.get(&record.id) + .expect("An invalid record was specified for an entity"); + + if self.query.can_visit_archetype(arch) { + let mut fetch = unsafe { self.query.fetch(self.world, arch.id, arch) }; + if fetch.can_visit_item(record.index) { + return Some(unsafe { fetch.get_item(record.index) }); + } + } + } + + None + } } \ No newline at end of file diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 8d32ecd..4970685 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -1,6 +1,6 @@ -use std::{collections::{HashMap, VecDeque}, any::TypeId, cell::{Ref, RefMut}}; +use std::{collections::{HashMap, VecDeque}, any::TypeId, cell::{Ref, RefMut}, ptr::NonNull}; -use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, component::Component, query::{ViewIter, View}, resource::ResourceData, Query, AsQuery, dynamic::{DynamicViewIter, QueryDynamicType, DynamicView}}; +use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, component::Component, query::{ViewIter, View}, resource::ResourceData, Query, AsQuery, dynamic::{DynamicViewIter, QueryDynamicType, DynamicView}, ViewOne, ComponentInfo, DynTypeId}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct EntityId(pub u64); @@ -16,15 +16,16 @@ pub struct Entity { pub(crate) generation: u64, } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Record { - id: ArchetypeId, - index: ArchetypeEntityId, + pub id: ArchetypeId, + pub index: ArchetypeEntityId, } pub struct World { pub(crate) archetypes: HashMap, next_archetype_id: ArchetypeId, - entity_index: HashMap, + pub(crate) entity_index: HashMap, dead_entities: VecDeque, next_entity_id: EntityId, resources: HashMap, @@ -67,7 +68,7 @@ impl World { // try to find an archetype let archetype = self.archetypes .values_mut() - .find(|a| a.is_archetype_for(bundle_types.clone())); + .find(|a| a.is_archetype_for(&bundle_types)); if let Some(archetype) = archetype { let arche_idx = archetype.add_entity(new_entity, bundle); @@ -115,6 +116,71 @@ impl World { } } + pub fn insert(&mut self, entity: Entity, bundle: B) + where + B: Bundle + { + // TODO: If the archetype has a single entity, add a component column for the new component instead of + // moving the entity to a brand new archetype. + + let record = *self.entity_index.get(&entity.id).unwrap(); + let current_arch = self.archetypes.get(&record.id).unwrap(); + + let mut col_types: Vec = current_arch.columns.iter().map(|c| c.info.type_id).collect(); + let orig_col = col_types.clone(); + col_types.extend(bundle.type_ids().into_iter()); + + let mut col_infos: Vec = current_arch.columns.iter().map(|c| c.info).collect(); + col_infos.extend(bundle.info().into_iter()); + + let col_ptrs: Vec<(NonNull, ComponentInfo)> = current_arch.columns.iter().map(|c| unsafe { (NonNull::new_unchecked(c.borrow_ptr().as_ptr()), c.info) }).collect(); + + if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&col_types)) { + let res_index = arch.reserve_one(entity); + + for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) { + unsafe { + let ptr = NonNull::new_unchecked(col_ptr.as_ptr() + .add(res_index.0 as usize * col_info.layout.size)); + let col = arch.get_column_mut(col_type).unwrap(); + col.set_at(res_index.0 as _, ptr); + } + } + + bundle.take(|data, type_id, _size| { + let col = arch.get_column_mut(type_id).unwrap(); + unsafe { col.set_at(res_index.0 as _, data); } + col.len += 1; + }); + + arch.entities.insert(entity, res_index); + + let new_record = Record { + id: arch.id, + index: res_index, + }; + self.entity_index.insert(entity.id, new_record); + } else { + 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); + + self.archetypes.insert(new_arch_id, archetype); + + // Create entity record and store it + let record = Record { + id: new_arch_id, + index: entity_arch_id, + }; + + self.entity_index.insert(entity.id, record); + } + + let current_arch = self.archetypes.get_mut(&record.id).unwrap(); + current_arch.remove_entity(entity); + } + + /// View into the world for a set of entities that satisfy the queries. pub fn view_iter<'a, T: 'static + Component + AsQuery>(&'a self) -> ViewIter<'a, T::Query> { let archetypes = self.archetypes.values().collect(); let v = View::new(self, T::Query::new(), archetypes); @@ -125,6 +191,12 @@ impl World { DynamicView::new(self) } + pub fn view_one<'a, T: 'static + AsQuery>(&'a self, entity: Entity) -> ViewOne<'a, T::Query> { + ViewOne::new(self, entity.id, T::Query::new()) + } + + //pub fn view_one(&self, entity: EntityId) -> + pub fn add_resource(&mut self, data: T) { self.resources.insert(TypeId::of::(), ResourceData::new(data)); } @@ -168,7 +240,7 @@ impl World { #[cfg(test)] mod tests { - use crate::tests::Vec2; + use crate::tests::{Vec2, Vec3}; use super::World; @@ -269,4 +341,36 @@ mod tests { assert!(world.try_get_resource_mut::().is_none()); assert_eq!(counter.0, 4582); } + + #[test] + fn insert_into_existing_archetype() { + let mut world = World::new(); + let e = world.spawn((Vec2::rand(),)); + world.spawn((Vec2::rand(),Vec3::rand())); + + world.insert(e, (Vec3::rand(),)); + + assert!(world.view_one::<&Vec3>(e).get().is_some()) + } + + #[test] + fn insert_into_new_archetype() { + let mut world = World::new(); + let e = world.spawn((Vec2::rand(),)); + + world.insert(e, (Vec3::rand(),)); + + assert!(world.view_one::<&Vec3>(e).get().is_some()) + } + + #[test] + fn view_one() { + let v = Vec2::rand(); + + let mut world = World::new(); + let e = world.spawn((v,)); + + let view = world.view_one::<&Vec2>(e); + assert_eq!(*view.get().unwrap(), v); + } } \ No newline at end of file