From 0f81adccbb91528ece796c5b4498771bbee6a14e Mon Sep 17 00:00:00 2001 From: copygirl Date: Fri, 27 Sep 2024 10:33:39 +0200 Subject: [PATCH] Add Archegraph type and a lot more --- src/ecs/archegraph.rs | 297 ++++++++++++++++++++++++++++++++++++++++++ src/ecs/archetable.rs | 88 +++++++------ src/ecs/archetype.rs | 20 ++- src/ecs/mod.rs | 1 + src/ecs/world.rs | 103 +++++++++++---- 5 files changed, 435 insertions(+), 74 deletions(-) create mode 100644 src/ecs/archegraph.rs diff --git a/src/ecs/archegraph.rs b/src/ecs/archegraph.rs new file mode 100644 index 0000000..f62c37c --- /dev/null +++ b/src/ecs/archegraph.rs @@ -0,0 +1,297 @@ +use std::alloc::Layout; +use std::collections::{HashMap, HashSet}; + +use super::archetable::{Archetable, EntityRow}; +use super::component::Component; +use super::entity::Entity; +use super::prelude::Archetype; +use super::world::World; + +pub struct Archegraph { + node_count: usize, + node_storage: Vec>, + // unused_indices: Vec, + type_lookup: HashMap, +} + +pub type NodeIndex = usize; + +pub struct Node { + index: NodeIndex, + archetable: Archetable, + with: HashMap, + without: HashMap, +} + +pub type GraphEntityIndex = super::entity_index::EntityIndex<(NodeIndex, EntityRow)>; + +impl Archegraph { + pub fn new() -> Self { + // Empty archetable is added in `World::bootstrap_archetable`. + Self { + node_count: 0, + node_storage: Vec::new(), + // unused_indices: Vec::new(), + type_lookup: HashMap::new(), + } + } + + pub fn bootstrap(&mut self, pairs: Vec<(Entity, Layout)>) { + // Inner function is just so call to `bootstrap` is prettier. + fn inner(graph: &mut Archegraph, pairs: Vec<(Entity, Layout)>, mut start_index: usize) { + // Iterate all the subsets of the pairs and initialize them, first. + // The `start_index` should ensure that no subset is bootstrapped twice. + for i in start_index..pairs.len() { + let mut pairs_subset = pairs.clone(); + pairs_subset.remove(i); + inner(graph, pairs_subset, start_index); + start_index += 1; + } + + let layouts = pairs.iter().map(|p| Some(p.1)).collect(); + let archetype = Archetype::new(pairs.iter().map(|p| p.0)); + let archetable = Archetable::new(archetype, layouts); + + // No entity lookups necessary during bootstrapping. + graph.insert(archetable, &GraphEntityIndex::EMPTY); + } + inner(self, pairs, 0); + } + + #[inline] + #[must_use] + pub fn node_count(&self) -> usize { + self.node_count + } + + #[must_use] + pub fn root(&self) -> &Node { + self.node_storage[0].as_ref().unwrap() + } + + #[must_use] + pub fn root_mut(&mut self) -> &mut Node { + self.node_storage[0].as_mut().unwrap() + } + + #[must_use] + pub fn get(&self, index: NodeIndex) -> Option<&Node> { + self.node_storage.get(index).and_then(Option::as_ref) + } + + #[must_use] + pub fn get_mut(&mut self, index: NodeIndex) -> Option<&mut Node> { + self.node_storage.get_mut(index).and_then(Option::as_mut) + } + + #[must_use] + pub fn lookup_or_create( + &mut self, + archetype: &Archetype, + entity_index: &GraphEntityIndex, + ) -> &mut Node { + if let Some(index) = self.type_lookup.get(&archetype) { + // SAFETY: `type_lookup` is guaranteed to point to a valid archetable. + self.node_storage[*index].as_mut().unwrap() + } else { + let layouts = self.get_layouts(archetype, entity_index); + let archetable = Archetable::new(archetype.clone(), layouts); + self.insert(archetable, entity_index) + } + } + + pub fn insert(&mut self, archetable: Archetable, entity_index: &GraphEntityIndex) -> &mut Node { + let archetype = archetable.archetype().clone(); + let index = self.reserve_archetable_index(); + let mut node = Node::new(index, archetable); + + self.type_lookup.insert(archetype, index); + self.fill_edges(&mut node, entity_index); + + let entry = &mut self.node_storage[index]; + *entry = Some(node); + entry.as_mut().unwrap() + } + + fn get_layout(&self, component: Component, entity_index: &GraphEntityIndex) -> Option { + // FIXME: Relations can be components too. + let entity: Entity = component.try_into().ok()?; + let (node_index, row) = entity_index.get(entity.id())?.data; + let archetable = self.get(node_index).unwrap().get(); + let component_index = archetable.archetype().position(World::LAYOUT)?; + let column = archetable.column_by_index(component_index).unwrap(); + let column = unsafe { column.as_typed_slice::() }; + Some(column[row]) + } + + fn get_layouts( + &self, + archetype: &Archetype, + entity_index: &GraphEntityIndex, + ) -> Vec> { + let iter = archetype.components().iter(); + iter.map(|c| self.get_layout(*c, entity_index)).collect() + } + + #[must_use] + fn reserve_archetable_index(&mut self) -> usize { + self.node_count += 1; + // self.unused_indices.pop().unwrap_or_else(|| { + self.node_storage.push(None); + self.node_storage.len() - 1 + // }) + } + + fn fill_edges(&mut self, node: &mut Node, entity_index: &GraphEntityIndex) { + let archetype = node.archetable.archetype(); + for component in archetype.components() { + let other_archetype = archetype.clone().without(*component); + let other_node = self.lookup_or_create(&other_archetype, entity_index); + node.without.insert(*component, other_node.index); + other_node.with.insert(*component, node.index); + } + } +} + +impl Node { + #[must_use] + fn new(index: NodeIndex, archetable: Archetable) -> Self { + Self { + index, + archetable, + with: HashMap::new(), + without: HashMap::new(), + } + } + + #[inline] + #[must_use] + pub fn index(&self) -> NodeIndex { + self.index + } + + #[inline] + #[must_use] + pub fn get(&self) -> &Archetable { + &self.archetable + } + + #[inline] + #[must_use] + pub fn get_mut(&mut self) -> &mut Archetable { + &mut self.archetable + } + + #[inline] + #[must_use] + pub fn iter<'a>(&self, archegraph: &'a Archegraph) -> impl Iterator { + GraphIter::new(archegraph, self.index) + } +} + +struct GraphIter<'a> { + archegraph: &'a Archegraph, + stack: Vec, + visited: HashSet, +} + +impl GraphIter<'_> { + fn new(archegraph: &Archegraph, start_index: NodeIndex) -> GraphIter { + GraphIter { + archegraph, + stack: vec![start_index], + visited: HashSet::new(), + } + } +} + +impl<'a> Iterator for GraphIter<'a> { + type Item = &'a Archetable; + + fn next(&mut self) -> Option { + while let Some(index) = self.stack.pop() { + if !self.visited.contains(&index) { + let node = self.archegraph.get(index).unwrap(); + self.stack.extend(node.with.values()); + self.visited.insert(index); + return Some(&node.archetable); + } + } + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bootstrap() { + let mut archegraph = Archegraph::new(); + + let a = Entity::new_checked(1, 0).unwrap(); + let b = Entity::new_checked(2, 0).unwrap(); + let c = Entity::new_checked(10, 0).unwrap(); + let d = Entity::new_checked(2000, 0).unwrap(); + + archegraph.bootstrap(vec![ + (a, Layout::new::()), + (b, Layout::new::()), + (c, Layout::new::()), + (d, Layout::new::()), + ]); + + // {a, b, c, d} has 16 subsets including {} (the empty root archetype) + // and {a, b, c, d} (archetype containing all components at the same time). + assert_eq!(16, archegraph.node_count()); + + let mut archetypes = archegraph + .root() + .iter(&archegraph) + .map(Archetable::archetype) + .map(Archetype::components) + .collect::>(); + + // Not guaranteed to iter in any specific order. + archetypes.sort(); + + assert_eq!( + vec![ + vec![], + vec![a], + vec![a, b], + vec![a, b, c], + vec![a, b, c, d], + vec![a, b, d], + vec![a, c], + vec![a, c, d], + vec![a, d], + vec![b], + vec![b, c], + vec![b, c, d], + vec![b, d], + vec![c], + vec![c, d], + vec![d], + ], + archetypes + ); + + // Ensure the columns' layouts are correct for a given Archetype. + let archetype = Archetype::new([a, c, d]); + let index = &GraphEntityIndex::EMPTY; + let table = &archegraph.lookup_or_create(&archetype, index).get(); + assert_eq!( + Layout::new::(), + table.column_by_index(0).unwrap().layout() + ); + assert_eq!( + Layout::new::(), + table.column_by_index(1).unwrap().layout() + ); + assert_eq!( + Layout::new::(), + table.column_by_index(2).unwrap().layout() + ); + } +} diff --git a/src/ecs/archetable.rs b/src/ecs/archetable.rs index 53ea9db..9418c3d 100644 --- a/src/ecs/archetable.rs +++ b/src/ecs/archetable.rs @@ -1,54 +1,44 @@ -use std::any::TypeId; +use std::alloc::Layout; use super::archetype::Archetype; use super::entity::Entity; -use super::world::World; - +use super::prelude::Component; use super::utility::RawComponentVec; pub struct Archetable { archetype: Archetype, - components: Vec, - columns: Vec, + components: Vec, + columns: Vec, entities: Vec, } +/// Represents an index into the `entities` vector of an `Archetable`. pub type EntityRow = usize; -struct ComponentInfo { - entity: Entity, - type_id: Option, - column_index: Option, +/// Represents an index into the `columns` vector of an `Archetable`. +pub type ValueComponentColumn = usize; + +pub struct ComponentData { + column_index: Option, } -struct Column { +struct ColumnData { data: RawComponentVec, component_index: usize, } impl Archetable { #[must_use] - pub fn new_empty() -> Self { - Self { - archetype: Archetype::EMPTY, - components: Vec::new(), - columns: Vec::new(), - entities: Vec::new(), - } - } + pub fn new(archetype: Archetype, layouts: Vec>) -> Self { + assert_eq!(archetype.components().len(), layouts.len()); - #[must_use] - pub fn new(world: &World, archetype: Archetype) -> Self { let mut components = Vec::new(); let mut columns = Vec::new(); - for component in archetype.components() { - let entity = (*component).try_into().unwrap(); - let (layout, type_id) = world.lookup_component_info(entity).unzip(); - + for layout in layouts { // If the entity has `Layout`, add a column for it. let column_index = if let Some(layout) = layout { - columns.push(Column { + columns.push(ColumnData { data: RawComponentVec::new(layout), component_index: components.len(), }); @@ -57,11 +47,7 @@ impl Archetable { None }; - components.push(ComponentInfo { - entity, - column_index, - type_id, - }); + components.push(ComponentData { column_index }); } Self { @@ -72,7 +58,13 @@ impl Archetable { } } - pub fn push(&mut self, entity: Entity) -> EntityRow { + #[inline] + #[must_use] + pub fn archetype(&self) -> &Archetype { + &self.archetype + } + + pub fn insert(&mut self, entity: Entity) -> EntityRow { self.entities.push(entity); let new_len = self.entities.len(); for column in self.columns.iter_mut() { @@ -92,23 +84,33 @@ impl Archetable { self.entities.get(row).copied() } - fn find_component_info(&self) -> Option<&ComponentInfo> { - let type_id = Some(TypeId::of::()); - self.components.iter().find(|e| e.type_id == type_id) + #[must_use] + pub fn column_by_index(&self, index: ValueComponentColumn) -> Option<&RawComponentVec> { + Some(&self.columns.get(index)?.data) + } + + #[must_use] + pub fn column_by_index_mut( + &mut self, + index: ValueComponentColumn, + ) -> Option<&mut RawComponentVec> { + Some(&mut self.columns.get_mut(index)?.data) } #[must_use] - pub fn get_component_column(&self) -> Option<&[T]> { - let info = self.find_component_info::()?; - let column = &self.columns[info.column_index?]; - Some(unsafe { column.data.as_typed_slice::() }) + pub fn column_by_component(&self, component: impl Into) -> Option<&RawComponentVec> { + let component_index = self.archetype.position(component.into())?; + let column_index = self.components[component_index].column_index?; + self.column_by_index(column_index) } #[must_use] - pub fn get_component_column_mut(&mut self) -> Option<&mut [T]> { - let info = self.find_component_info::()?; - let column_index = info.column_index?; // Avoid borrow. - let column = &mut self.columns[column_index]; - Some(unsafe { column.data.as_typed_slice_mut::() }) + pub fn column_by_component_mut( + &mut self, + component: impl Into, + ) -> Option<&mut RawComponentVec> { + let component_index = self.archetype.position(component.into())?; + let column_index = self.components[component_index].column_index?; + self.column_by_index_mut(column_index) } } diff --git a/src/ecs/archetype.rs b/src/ecs/archetype.rs index fdebfb5..e792aab 100644 --- a/src/ecs/archetype.rs +++ b/src/ecs/archetype.rs @@ -1,9 +1,9 @@ use super::component::Component; -/// Represents a set of components an [`Entity`] may have. +/// Represents a sorted set of components an [`Entity`] may have. /// /// [`Entity`]: super::entity::Entity -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct Archetype { components: Vec, } @@ -15,8 +15,8 @@ impl Archetype { #[must_use] pub fn new(iter: impl IntoIterator>) -> Self { - let iter = iter.into_iter().map(Into::into).collect(); - let mut components: Vec<_> = iter; + let iter = iter.into_iter().map(Into::into); + let mut components: Vec<_> = iter.collect(); components.sort(); components.dedup(); Self { components } @@ -33,6 +33,11 @@ impl Archetype { self.components.binary_search(&component.into()).is_ok() } + #[must_use] + pub fn position(&self, component: impl Into) -> Option { + self.components.binary_search(&component.into()).ok() + } + pub fn add(&mut self, component: impl Into) { let component = component.into(); if let Err(index) = self.components.binary_search(&component) { @@ -60,6 +65,12 @@ impl Archetype { self.remove(component); self } + + #[inline] + #[must_use] + pub fn iter(&self) -> std::slice::Iter<'_, Component> { + self.components.iter() + } } impl FromIterator for Archetype @@ -76,7 +87,6 @@ impl IntoIterator for Archetype { type Item = Component; type IntoIter = std::vec::IntoIter; - #[inline] fn into_iter(self) -> Self::IntoIter { self.components.into_iter() } diff --git a/src/ecs/mod.rs b/src/ecs/mod.rs index 925fd7b..e849728 100644 --- a/src/ecs/mod.rs +++ b/src/ecs/mod.rs @@ -1,3 +1,4 @@ +pub mod archegraph; pub mod archetable; pub mod archetype; pub mod component; diff --git a/src/ecs/world.rs b/src/ecs/world.rs index 3f8677a..7f00fe2 100644 --- a/src/ecs/world.rs +++ b/src/ecs/world.rs @@ -2,28 +2,35 @@ use std::alloc::Layout; use std::any::TypeId; use std::collections::HashMap; -use super::archetable::Archetable; +use crate::ecs::prelude::Archetype; + +use super::archegraph::{Archegraph, GraphEntityIndex}; use super::entity::{Entity, EntityId}; use super::entity_index::EntityIndex; pub struct World { - entity_index: EntityIndex, - empty_archetable: Archetable, - type_id_lookup: HashMap, - // Temporary. To be removed once we can build and use Archetables that hold this information. - component_lookup: HashMap, + entity_index: GraphEntityIndex, + archegraph: Archegraph, + typeid_lookup: HashMap, } impl World { + pub const LAYOUT: Entity = unsafe { Entity::new_unchecked(1, 0) }; + pub const TYPEID: Entity = unsafe { Entity::new_unchecked(2, 0) }; + #[must_use] pub fn new() -> Self { let mut world = Self { entity_index: EntityIndex::new(), - empty_archetable: Archetable::new_empty(), - type_id_lookup: HashMap::new(), - component_lookup: HashMap::new(), + archegraph: Archegraph::new(), + typeid_lookup: HashMap::new(), }; + world.archegraph.bootstrap(vec![ + (World::LAYOUT, Layout::new::()), + (World::TYPEID, Layout::new::()), + ]); + world.register_component::(); world.register_component::(); @@ -37,34 +44,58 @@ impl World { #[must_use] pub fn lookup_by_type(&self) -> Option { - self.type_id_lookup.get(&TypeId::of::()).copied() - } - - #[must_use] - pub fn lookup_component_info(&self, entity: Entity) -> Option<(Layout, TypeId)> { - self.component_lookup.get(&entity).copied() + self.typeid_lookup.get(&TypeId::of::()).copied() } pub fn register_component(&mut self) -> Entity { - assert_ne!(0, size_of::()); - let entity = self.create(); - let component_info = (Layout::new::(), TypeId::of::()); - self.component_lookup.insert(entity, component_info); + let entity = if size_of::() > 0 { + let archetype = Archetype::new([Self::LAYOUT, Self::TYPEID]); + let entity = self.create(&archetype); + entity + } else { + let archetype = Archetype::new([Self::TYPEID]); + let entity = self.create(&archetype); + entity + }; + + let (node_index, row) = self.entity_index.get(entity.id()).unwrap().data; + let archetable = self.archegraph.get_mut(node_index).unwrap().get_mut(); + + if size_of::() > 0 { + let layout_column = archetable.column_by_component_mut(World::LAYOUT).unwrap(); + let layout_column = unsafe { layout_column.as_typed_slice_mut::() }; + layout_column[row] = Layout::new::(); + } + + let typeid = TypeId::of::(); + self.typeid_lookup.insert(typeid, entity); + + let typeid_column = archetable.column_by_component_mut(World::TYPEID).unwrap(); + let typeid_column = unsafe { typeid_column.as_typed_slice_mut::() }; + typeid_column[row] = typeid; + entity } - #[must_use] - pub fn create(&mut self) -> Entity { + pub fn create(&mut self, archetype: &Archetype) -> Entity { + let entities = &self.entity_index; + let graph_node = self.archegraph.lookup_or_create(archetype, entities); + let archetable = graph_node.get_mut(); + let entry = self.entity_index.create(); - let row = self.empty_archetable.push(entry.entity); - entry.update(row) + let row = archetable.insert(entry.entity); + + entry.update((graph_node.index(), row)) } pub fn destroy(&mut self, entity: Entity) -> bool { - if let Ok(row) = self.entity_index.destroy(entity) { - if let Some(swapped) = self.empty_archetable.swap_remove(row) { + if let Ok((node_index, row)) = self.entity_index.destroy(entity) { + let graph_node = self.archegraph.get_mut(node_index).unwrap(); + let archetable = graph_node.get_mut(); + if let Some(swapped) = archetable.swap_remove(row) { // Assume that `swapped` we got from the archetype table is alive. - self.entity_index.get(swapped.id()).unwrap().data = row; + let mut entry = self.entity_index.get(swapped.id()).unwrap(); + entry.data = (graph_node.index(), row); } true } else { @@ -72,3 +103,23 @@ impl World { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn world_new_check() { + let world = World::new(); + let (node_index, row) = world.entity_index.get(World::LAYOUT.id()).unwrap().data; + let archetable = world.archegraph.get(node_index).unwrap().get(); + + let layout_column = archetable.column_by_component(World::LAYOUT).unwrap(); + let layout_column = unsafe { layout_column.as_typed_slice::() }; + assert_eq!(Layout::new::(), layout_column[row]); + + let typeid_column = archetable.column_by_component(World::TYPEID).unwrap(); + let typeid_column = unsafe { typeid_column.as_typed_slice::() }; + assert_eq!(TypeId::of::(), typeid_column[row]); + } +}