Implement inserting components into entities, ViewOne

This commit is contained in:
SeanOMik 2023-12-14 00:25:09 -05:00
parent da206b4824
commit 8c8e7dfd7d
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
4 changed files with 297 additions and 25 deletions

View File

@ -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<NonNull<u8>>,
len: usize,
capacity: usize,
pub data: RefCell<NonNull<u8>>,
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)));
}
@ -271,13 +276,19 @@ impl Archetype {
}
}
// 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<DynTypeId>) -> bool {
pub(crate) fn is_archetype_for(&self, types: &Vec<DynTypeId>) -> 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<B>(&self, into_arch: &mut Archetype, entity: Entity, new_components: B)
where
B: Bundle
{
let new_index = into_arch.reserve_one(entity);
//let infos: Vec<ComponentInfo> = 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::<u32>().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::<Vec3>()).unwrap();
let from_col = unsafe { new_col.get::<Vec3>(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::<Vec2>()).unwrap();
let from_col = unsafe { new_col.get::<Vec2>(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
}
}

View File

@ -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<DynTypeId> {
// 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<u8>, 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<C>(&mut self, comp: C)
where
C: Component
{
let info = ComponentInfo::new::<C>();
let data = NonNull::from(&comp).cast::<u8>();
self.bundle.push((data, info));
// 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);
let layout = Layout::new::<C>();
let alloc_ptr = NonNull::new_unchecked(std::alloc::alloc(layout)).cast::<C>();
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

View File

@ -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};
@ -106,3 +106,35 @@ 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<Q::Item<'a>> {
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
}
}

View File

@ -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<ArchetypeId, Archetype>,
next_archetype_id: ArchetypeId,
entity_index: HashMap<EntityId, Record>,
pub(crate) entity_index: HashMap<EntityId, Record>,
dead_entities: VecDeque<Entity>,
next_entity_id: EntityId,
resources: HashMap<TypeId, ResourceData>,
@ -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<B>(&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<DynTypeId> = 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<ComponentInfo> = current_arch.columns.iter().map(|c| c.info).collect();
col_infos.extend(bundle.info().into_iter());
let col_ptrs: 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()) {
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<T: 'static>(&mut self, data: T) {
self.resources.insert(TypeId::of::<T>(), 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::<SimpleCounter>().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);
}
}