Add Archegraph type and a lot more

wip/graph
copygirl 2 months ago
parent 017ff31790
commit 0f81adccbb
  1. 297
      src/ecs/archegraph.rs
  2. 88
      src/ecs/archetable.rs
  3. 20
      src/ecs/archetype.rs
  4. 1
      src/ecs/mod.rs
  5. 103
      src/ecs/world.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<Option<Node>>,
// unused_indices: Vec<NodeIndex>,
type_lookup: HashMap<Archetype, NodeIndex>,
}
pub type NodeIndex = usize;
pub struct Node {
index: NodeIndex,
archetable: Archetable,
with: HashMap<Component, NodeIndex>,
without: HashMap<Component, NodeIndex>,
}
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<Layout> {
// 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::<Layout>() };
Some(column[row])
}
fn get_layouts(
&self,
archetype: &Archetype,
entity_index: &GraphEntityIndex,
) -> Vec<Option<Layout>> {
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<Item = &'a Archetable> {
GraphIter::new(archegraph, self.index)
}
}
struct GraphIter<'a> {
archegraph: &'a Archegraph,
stack: Vec<NodeIndex>,
visited: HashSet<NodeIndex>,
}
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<Self::Item> {
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::<u8>()),
(b, Layout::new::<u16>()),
(c, Layout::new::<u32>()),
(d, Layout::new::<u64>()),
]);
// {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::<Vec<_>>();
// 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::<u8>(),
table.column_by_index(0).unwrap().layout()
);
assert_eq!(
Layout::new::<u32>(),
table.column_by_index(1).unwrap().layout()
);
assert_eq!(
Layout::new::<u64>(),
table.column_by_index(2).unwrap().layout()
);
}
}

@ -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<ComponentInfo>,
columns: Vec<Column>,
components: Vec<ComponentData>,
columns: Vec<ColumnData>,
entities: Vec<Entity>,
}
/// Represents an index into the `entities` vector of an `Archetable`.
pub type EntityRow = usize;
struct ComponentInfo {
entity: Entity,
type_id: Option<TypeId>,
column_index: Option<usize>,
/// Represents an index into the `columns` vector of an `Archetable`.
pub type ValueComponentColumn = usize;
pub struct ComponentData {
column_index: Option<ValueComponentColumn>,
}
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<Option<Layout>>) -> 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<T: 'static>(&self) -> Option<&ComponentInfo> {
let type_id = Some(TypeId::of::<T>());
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<T: 'static>(&self) -> Option<&[T]> {
let info = self.find_component_info::<T>()?;
let column = &self.columns[info.column_index?];
Some(unsafe { column.data.as_typed_slice::<T>() })
pub fn column_by_component(&self, component: impl Into<Component>) -> 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<T: 'static>(&mut self) -> Option<&mut [T]> {
let info = self.find_component_info::<T>()?;
let column_index = info.column_index?; // Avoid borrow.
let column = &mut self.columns[column_index];
Some(unsafe { column.data.as_typed_slice_mut::<T>() })
pub fn column_by_component_mut(
&mut self,
component: impl Into<Component>,
) -> 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)
}
}

@ -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<Component>,
}
@ -15,8 +15,8 @@ impl Archetype {
#[must_use]
pub fn new(iter: impl IntoIterator<Item = impl Into<Component>>) -> 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<Component>) -> Option<usize> {
self.components.binary_search(&component.into()).ok()
}
pub fn add(&mut self, component: impl Into<Component>) {
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<T> FromIterator<T> for Archetype
@ -76,7 +87,6 @@ impl IntoIterator for Archetype {
type Item = Component;
type IntoIter = std::vec::IntoIter<Self::Item>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.components.into_iter()
}

@ -1,3 +1,4 @@
pub mod archegraph;
pub mod archetable;
pub mod archetype;
pub mod component;

@ -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<usize>,
empty_archetable: Archetable,
type_id_lookup: HashMap<TypeId, Entity>,
// Temporary. To be removed once we can build and use Archetables that hold this information.
component_lookup: HashMap<Entity, (Layout, TypeId)>,
entity_index: GraphEntityIndex,
archegraph: Archegraph,
typeid_lookup: HashMap<TypeId, Entity>,
}
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::<Layout>()),
(World::TYPEID, Layout::new::<TypeId>()),
]);
world.register_component::<Layout>();
world.register_component::<TypeId>();
@ -37,34 +44,58 @@ impl World {
#[must_use]
pub fn lookup_by_type<T: 'static>(&self) -> Option<Entity> {
self.type_id_lookup.get(&TypeId::of::<T>()).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::<T>()).copied()
}
pub fn register_component<T: 'static>(&mut self) -> Entity {
assert_ne!(0, size_of::<T>());
let entity = self.create();
let component_info = (Layout::new::<T>(), TypeId::of::<T>());
self.component_lookup.insert(entity, component_info);
let entity = if size_of::<T>() > 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::<T>() > 0 {
let layout_column = archetable.column_by_component_mut(World::LAYOUT).unwrap();
let layout_column = unsafe { layout_column.as_typed_slice_mut::<Layout>() };
layout_column[row] = Layout::new::<T>();
}
let typeid = TypeId::of::<T>();
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>() };
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::<Layout>() };
assert_eq!(Layout::new::<Layout>(), layout_column[row]);
let typeid_column = archetable.column_by_component(World::TYPEID).unwrap();
let typeid_column = unsafe { typeid_column.as_typed_slice::<TypeId>() };
assert_eq!(TypeId::of::<Layout>(), typeid_column[row]);
}
}

Loading…
Cancel
Save