Implement inserting components into entities, ViewOne
This commit is contained in:
parent
da206b4824
commit
8c8e7dfd7d
|
@ -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 {
|
||||
self.columns.iter().all(|c| types.contains(&c.info.type_id))
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue