From 574a2ee54cb24a26ec1a4df219dedc954e437c9a Mon Sep 17 00:00:00 2001 From: copygirl Date: Tue, 24 Sep 2024 10:58:25 +0200 Subject: [PATCH] WIP --- .vscode/launch.json | 27 +++++++ src/ecs/archegraph.rs | 160 ++++++++++++++++++++++++++++++++++++++++++ src/ecs/archetable.rs | 63 +++++++---------- src/ecs/archetype.rs | 67 ++++++++++++++---- src/ecs/component.rs | 2 +- src/ecs/mod.rs | 1 + src/ecs/world.rs | 64 ++++++++++------- 7 files changed, 308 insertions(+), 76 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 src/ecs/archegraph.rs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5be08d5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'gaemstone'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=gaemstone" + ], + "filter": { + "name": "gaemstone", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/src/ecs/archegraph.rs b/src/ecs/archegraph.rs new file mode 100644 index 0000000..f1488af --- /dev/null +++ b/src/ecs/archegraph.rs @@ -0,0 +1,160 @@ +use std::alloc::Layout; +use std::collections::HashMap; + +use super::archetable::{Archetable, EntityRow}; +use super::component::Component; +use super::entity::Entity; +use super::entity_index::EntityIndex; +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 = EntityIndex<(NodeIndex, EntityRow)>; + +impl Archegraph { + pub fn new() -> Self { + let empty_node = Node::new(0, Archetable::new_empty()); + let mut type_lookup = HashMap::new(); + type_lookup.insert(Archetype::EMPTY, 0); + Self { + node_count: 1, + node_storage: vec![Some(empty_node)], + // unused_indices: Vec::new(), + type_lookup, + } + } + + #[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 index = self.reserve_archetable_index(); + let layouts = self.get_layouts(archetype, entity_index); + let archetable = Archetable::new(archetype.clone(), layouts); + let mut node = Node::new(index, archetable); + + self.type_lookup.insert(archetype.clone(), 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.get_column(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 + } +} diff --git a/src/ecs/archetable.rs b/src/ecs/archetable.rs index 53ea9db..4dd8982 100644 --- a/src/ecs/archetable.rs +++ b/src/ecs/archetable.rs @@ -1,27 +1,27 @@ -use std::any::TypeId; +use std::alloc::Layout; use super::archetype::Archetype; use super::entity::Entity; -use super::world::World; - 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 `colums` vector of an `Archetable`. +pub type ValueComponentColumn = usize; + +pub struct ComponentData { + column_index: Option, } -struct Column { +struct ColumnData { data: RawComponentVec, component_index: usize, } @@ -38,17 +38,16 @@ impl Archetable { } #[must_use] - pub fn new(world: &World, archetype: Archetype) -> Self { + pub fn new(archetype: Archetype, layouts: Vec>) -> Self { + assert_eq!(archetype.components().len(), layouts.len()); + 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 +56,7 @@ impl Archetable { None }; - components.push(ComponentInfo { - entity, - column_index, - type_id, - }); + components.push(ComponentData { column_index }); } Self { @@ -72,7 +67,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 +93,13 @@ 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 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 get_column(&self, index: ValueComponentColumn) -> Option<&RawComponentVec> { + Some(&self.columns.get(index)?.data) } #[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 get_column_mut(&mut self, index: ValueComponentColumn) -> Option<&mut RawComponentVec> { + Some(&mut self.columns.get_mut(index)?.data) } } diff --git a/src/ecs/archetype.rs b/src/ecs/archetype.rs index fdebfb5..3fbe9d6 100644 --- a/src/ecs/archetype.rs +++ b/src/ecs/archetype.rs @@ -1,6 +1,6 @@ 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)] @@ -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,52 @@ impl Archetype { self.remove(component); self } + + // #[must_use] + // pub fn union(&self, other: &Self) -> Self { + // let mut components = Vec::new(); + // let mut self_iter = self.components.iter(); + // let mut other_iter = other.components.iter(); + + // { + // let mut maybe_a = self_iter.next(); + // let mut maybe_b = other_iter.next(); + + // while let (Some(a), Some(b)) = (maybe_a, maybe_b) { + // use std::cmp::Ordering::*; + // components.push(match a.cmp(b) { + // Less => { + // maybe_a = self_iter.next(); + // *a + // } + // Equal => { + // maybe_a = self_iter.next(); + // maybe_b = other_iter.next(); + // *a + // } + // Greater => { + // maybe_b = other_iter.next(); + // *b + // } + // }) + // } + + // if let Some(a) = maybe_a { + // components.push(*a); + // } + // if let Some(b) = maybe_b { + // components.push(*b); + // } + // } + + // for a in self_iter { + // components.push(*a); + // } + // for b in other_iter { + // components.push(*b); + // } + // Self { components } + // } } impl FromIterator for Archetype @@ -71,13 +122,3 @@ where Self::new(iter) } } - -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/component.rs b/src/ecs/component.rs index 6b05bea..426b01c 100644 --- a/src/ecs/component.rs +++ b/src/ecs/component.rs @@ -96,7 +96,7 @@ impl std::hash::Hash for Component { } impl Flags { - // NOTE: When changing this be sure to update documentation below. + // NOTE: When changing this be sure to update documentation. pub(crate) const MASK: u32 = 0xF0000000; /// Extracts only the flags from the specified `u32`. 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..7fc02b3 100644 --- a/src/ecs/world.rs +++ b/src/ecs/world.rs @@ -2,26 +2,28 @@ 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.register_component::(); @@ -37,34 +39,44 @@ 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 type_id = TypeId::of::(); + self.typeid_lookup.insert(type_id, entity); + entity } - #[must_use] - pub fn create(&mut self) -> Entity { + pub fn create(&mut self, archetype: &Archetype) -> Entity { + let graph_node = self + .archegraph + .lookup_or_create(archetype, &self.entity_index); + 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 {