Compare commits

..

7 Commits

  1. 27
      .vscode/launch.json
  2. 265
      src/ecs/archegraph.rs
  3. 65
      src/ecs/archetable.rs
  4. 61
      src/ecs/archetype.rs
  5. 52
      src/ecs/component.rs
  6. 7
      src/ecs/entity.rs
  7. 4
      src/ecs/entity_index.rs
  8. 9
      src/ecs/relation.rs
  9. 52
      src/ecs/world.rs

@ -1,27 +0,0 @@
{
// 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}"
}
]
}

@ -1,44 +1,82 @@
use std::alloc::Layout;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use super::archetable::{Archetable, EntityRow};
use super::archetype::Archetype;
use super::component::Component;
use super::entity::Entity;
use super::entity_index::EntityIndex;
use super::prelude::Archetype;
use super::world::World;
/// Graph of [`Archetable`]s contained within [`Node`]s.
///
/// Allows for traversal by of `Archetables` by their [`Archetype`], one
/// [`Component`] at a time. For example to get all [`entities`][`Entity`] with
/// both `Position` and `Velocity` you would start at `root()`, then traverse
/// to `Position`, then `Velocity`, or the other way around, since you end up
/// at the same `Node` either way. You can then `iter()` all `Archetables` that
/// have those same components.
pub struct Archegraph {
node_count: usize,
node_storage: Vec<Option<Node>>,
node_storage: Vec<Option<NodeData>>,
archetable_storage: Vec<Option<Archetable>>,
// unused_indices: Vec<NodeIndex>,
type_lookup: HashMap<Archetype, NodeIndex>,
}
pub type NodeIndex = usize;
pub type GraphEntityIndex = super::entity_index::EntityIndex<(NodeIndex, EntityRow)>;
pub struct Node {
index: NodeIndex,
archetable: Archetable,
with: HashMap<Component, NodeIndex>,
without: HashMap<Component, NodeIndex>,
type NodeIndex = usize;
pub struct Node<'a> {
archegraph: &'a Archegraph,
node_index: NodeIndex,
}
pub struct NodeMut<'a> {
archegraph: &'a mut Archegraph,
node_index: NodeIndex,
}
pub type GraphEntityIndex = EntityIndex<(NodeIndex, EntityRow)>;
#[derive(Default)]
struct NodeData {
add: HashMap<Component, NodeIndex>,
remove: HashMap<Component, NodeIndex>,
}
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);
// Empty archetable is added in `World::bootstrap_archetable`.
Self {
node_count: 1,
node_storage: vec![Some(empty_node)],
node_count: 0,
node_storage: Vec::new(),
archetable_storage: Vec::new(),
// unused_indices: Vec::new(),
type_lookup,
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 {
@ -46,22 +84,22 @@ impl Archegraph {
}
#[must_use]
pub fn root(&self) -> &Node {
pub fn root(&self) -> &NodeData {
self.node_storage[0].as_ref().unwrap()
}
#[must_use]
pub fn root_mut(&mut self) -> &mut Node {
pub fn root_mut(&mut self) -> &mut NodeData {
self.node_storage[0].as_mut().unwrap()
}
#[must_use]
pub fn get(&self, index: NodeIndex) -> Option<&Node> {
pub fn get(&self, index: NodeIndex) -> Option<&NodeData> {
self.node_storage.get(index).and_then(Option::as_ref)
}
#[must_use]
pub fn get_mut(&mut self, index: NodeIndex) -> Option<&mut Node> {
pub fn get_mut(&mut self, index: NodeIndex) -> Option<&mut NodeData> {
self.node_storage.get_mut(index).and_then(Option::as_mut)
}
@ -70,23 +108,29 @@ impl Archegraph {
&mut self,
archetype: &Archetype,
entity_index: &GraphEntityIndex,
) -> &mut Node {
) -> NodeMut {
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.insert(archetable, entity_index)
}
}
self.type_lookup.insert(archetype.clone(), index);
self.fill_edges(&mut node, entity_index);
pub fn insert(&mut self, archetable: Archetable, entity_index: &GraphEntityIndex) -> NodeMut {
let archetype = archetable.archetype().clone();
let index = self.reserve_archetable_index();
let entry = &mut self.node_storage[index];
*entry = Some(node);
entry.as_mut().unwrap()
}
let mut node = Default::default();
self.fill_edges(&mut node, entity_index);
self.node_storage[index] = Some(node);
self.archetable_storage[index] = Some(archetable);
self.type_lookup.insert(archetype, index);
()
}
fn get_layout(&self, component: Component, entity_index: &GraphEntityIndex) -> Option<Layout> {
@ -95,7 +139,7 @@ impl Archegraph {
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 = archetable.column_by_index(component_index).unwrap();
let column = unsafe { column.as_typed_slice::<Layout>() };
Some(column[row])
}
@ -118,43 +162,162 @@ impl Archegraph {
// })
}
fn fill_edges(&mut self, node: &mut Node, entity_index: &GraphEntityIndex) {
fn fill_edges(&mut self, node: &mut NodeData, 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);
node.remove.insert(*component, other_node.index);
other_node.add.insert(*component, node.index);
}
}
}
impl Node {
impl<'a> Node<'a> {
#[must_use]
fn new(index: NodeIndex, archetable: Archetable) -> Self {
Self {
index,
archetable,
with: HashMap::new(),
without: HashMap::new(),
}
pub fn iter(&'a self) -> impl Iterator<Item = &Archetable> {
let storage = &self.archegraph.archetable_storage;
GraphIter::new(&self.archegraph.node_storage, self.node_index)
.map(|index| storage[index].as_ref().unwrap())
}
#[inline]
#[must_use]
pub fn index(&self) -> NodeIndex {
self.index
pub fn get(&self) -> &Archetable {
let storage = &self.archegraph.archetable_storage;
storage[self.node_index].as_ref().unwrap()
}
}
#[inline]
impl<'a> NodeMut<'a> {
#[must_use]
pub fn get(&self) -> &Archetable {
&self.archetable
pub fn iter(&'a mut self) -> impl Iterator<Item = &'a mut Archetable> {
let node_storage = &self.archegraph.node_storage;
let archetable_storage = &mut self.archegraph.archetable_storage;
GraphIter::new(node_storage, self.node_index)
.map(|index| archetable_storage[index].as_mut().unwrap())
}
#[inline]
#[must_use]
pub fn get_mut(&mut self) -> &mut Archetable {
&mut self.archetable
pub fn get(&mut self) -> &mut Archetable {
let storage = &mut self.archegraph.archetable_storage;
storage[self.node_index].as_mut().unwrap()
}
}
struct GraphIter<'a> {
node_storage: &'a Vec<Option<NodeData>>,
stack: Vec<NodeIndex>,
visited: HashSet<NodeIndex>,
}
impl GraphIter<'_> {
fn new(node_storage: &Vec<Option<NodeData>>, start_index: NodeIndex) -> GraphIter {
GraphIter {
node_storage,
stack: vec![start_index],
visited: HashSet::new(),
}
}
}
impl<'a> Iterator for GraphIter<'a> {
type Item = NodeIndex;
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.add.values());
self.visited.insert(index);
return Some(index);
}
}
None
}
}
impl NodeData {
#[must_use]
fn new(archetable: Archetable) -> Self {
Self {
archetable,
add: HashMap::new(),
remove: HashMap::new(),
}
}
}
#[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,9 +1,19 @@
use std::alloc::Layout;
use super::archetype::Archetype;
use super::component::Component;
use super::entity::Entity;
use super::utility::RawComponentVec;
/// Table data structure that holds [`Entities`](`Entity`) with a
/// specific [`Archetype`] and their [`Component`] values, if any.
///
/// Each row in this table refers to an alive `Entity`, which may only be in
/// one `Archetable` at a time, per [`World`]. There is also a column for each
/// "value component" – a component that holds a value. Components that don't
/// hold a value, known as tags, don't have an associated column.
///
/// [`World`]: super::world::World
pub struct Archetable {
archetype: Archetype,
components: Vec<ComponentData>,
@ -14,7 +24,7 @@ pub struct Archetable {
/// Represents an index into the `entities` vector of an `Archetable`.
pub type EntityRow = usize;
/// Represents an index into the `colums` vector of an `Archetable`.
/// Represents an index into the `columns` vector of an `Archetable`.
pub type ValueComponentColumn = usize;
pub struct ComponentData {
@ -23,20 +33,10 @@ pub struct ComponentData {
struct ColumnData {
data: RawComponentVec,
component_index: usize,
// 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(),
}
}
#[must_use]
pub fn new(archetype: Archetype, layouts: Vec<Option<Layout>>) -> Self {
assert_eq!(archetype.components().len(), layouts.len());
@ -45,17 +45,14 @@ impl Archetable {
let mut columns = Vec::new();
for layout in layouts {
// If the entity has `Layout`, add a column for it.
let column_index = if let Some(layout) = layout {
// If component has `Layout`, add a column for it.
let column_index = layout.map(|layout| {
columns.push(ColumnData {
data: RawComponentVec::new(layout),
component_index: components.len(),
// component_index: components.len(),
});
Some(columns.len() - 1)
} else {
None
};
columns.len() - 1
});
components.push(ComponentData { column_index });
}
@ -73,7 +70,7 @@ impl Archetable {
&self.archetype
}
pub fn insert(&mut self, entity: Entity) -> EntityRow {
pub(crate) fn insert(&mut self, entity: Entity) -> EntityRow {
self.entities.push(entity);
let new_len = self.entities.len();
for column in self.columns.iter_mut() {
@ -85,7 +82,7 @@ impl Archetable {
/// Removes an entity from the specified `row`, including its component values.
/// Potentially swaps the last alive entity into the vacant spot, then shrinks the `entities` vector.
/// If a swap occurred, the entity swapped into `row` is returned, so references to it can be updated.
pub fn swap_remove(&mut self, row: EntityRow) -> Option<Entity> {
pub(crate) fn swap_remove(&mut self, row: EntityRow) -> Option<Entity> {
self.entities.swap_remove(row);
for column in self.columns.iter_mut() {
column.data.swap_remove(row);
@ -94,12 +91,32 @@ impl Archetable {
}
#[must_use]
pub fn get_column(&self, index: ValueComponentColumn) -> Option<&RawComponentVec> {
pub fn column_by_index(&self, index: ValueComponentColumn) -> Option<&RawComponentVec> {
Some(&self.columns.get(index)?.data)
}
#[must_use]
pub fn get_column_mut(&mut self, index: ValueComponentColumn) -> Option<&mut RawComponentVec> {
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 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 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)
}
}

@ -3,7 +3,7 @@ use super::component::Component;
/// 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>,
}
@ -66,51 +66,11 @@ impl Archetype {
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 }
// }
#[inline]
#[must_use]
pub fn iter(&self) -> std::slice::Iter<'_, Component> {
self.components.iter()
}
}
impl<T> FromIterator<T> for Archetype
@ -122,3 +82,12 @@ where
Self::new(iter)
}
}
impl IntoIterator for Archetype {
type Item = Component;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.components.into_iter()
}
}

@ -1,3 +1,5 @@
use std::num::NonZeroU32;
/// Components are plain identifiers which represent anything that can be
/// added to an [`Entity`], which includes other entities and [`Relation`]s.
///
@ -13,7 +15,7 @@
#[repr(C)]
#[derive(Copy, Clone, Eq)]
pub struct Component {
low: u32,
low: NonZeroU32,
high: u32,
}
@ -95,6 +97,23 @@ impl std::hash::Hash for Component {
}
}
impl std::fmt::Debug for Component {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_entity() {
use super::entity::Entity;
unsafe { Entity::from_bits_unchecked(self.to_bits()).fmt(f) }
} else if self.is_relation() {
use super::relation::Relation;
unsafe { Relation::from_bits_unchecked(self.to_bits()).fmt(f) }
} else {
f.debug_struct("Component")
.field("low", &self.low)
.field("high", &self.high)
.finish()
}
}
}
impl Flags {
// NOTE: When changing this be sure to update documentation.
pub(crate) const MASK: u32 = 0xF0000000;
@ -148,7 +167,9 @@ mod tests {
let entity = Entity::new_checked(1337, 42).unwrap();
let component: Component = entity.into();
assert!(component.is_entity());
assert_eq!(entity, component.try_into().unwrap());
let back_to_entity: Entity = component.try_into().unwrap();
assert_eq!(entity, back_to_entity);
}
#[test]
@ -156,6 +177,31 @@ mod tests {
let relation = Relation::new_checked(20, 21).unwrap();
let component: Component = relation.into();
assert!(component.is_relation());
assert_eq!(relation, component.try_into().unwrap());
let back_to_relation: Relation = component.try_into().unwrap();
assert_eq!(relation, back_to_relation);
}
#[test]
fn debug() {
// Component, as long as it's a valid Entity or Relation, will format as such.
// In cases where it's neither (not a valid Component), it has a default formatting.
let entity: Component = Entity::new_checked(69, 1337).unwrap().into();
let relation: Component = Relation::new_checked(420, 9001).unwrap().into();
let invalid = unsafe { Component::from_bits_unchecked(0x20000000_00000001) };
assert_eq!(
"Entity { id: 69, gen: Generation(1337) }",
format!("{entity:?}")
);
assert_eq!(
"Relation { target: 9001, kind: 420 }",
format!("{relation:?}")
);
assert_eq!(
"Component { low: 1, high: 536870912 }",
format!("{invalid:?}")
);
}
}

@ -140,6 +140,13 @@ impl PartialEq for Entity {
}
}
impl PartialEq<Component> for Entity {
#[inline]
fn eq(&self, other: &Component) -> bool {
self.to_bits() == other.to_bits()
}
}
impl Ord for Entity {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {

@ -58,8 +58,10 @@ pub struct EntityIndex<T: Copy + Default> {
}
impl<T: Copy + Default> EntityIndex<T> {
pub const EMPTY: EntityIndex<T> = Self::new();
#[must_use]
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
dense: Vec::new(),
alive_count: 0,

@ -81,7 +81,7 @@ impl Relation {
let target = bits as u32;
let high = (bits >> 32) as u32;
if let (kind, Flags::RELATION) = Flags::unpack(high)? {
Self::new_checked(kind, target)
Self::new_checked(kind, target)
} else {
None
}
@ -122,6 +122,13 @@ impl PartialEq for Relation {
}
}
impl PartialEq<Component> for Relation {
#[inline]
fn eq(&self, other: &Component) -> bool {
self.to_bits() == other.to_bits()
}
}
impl Ord for Relation {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {

@ -2,9 +2,8 @@ use std::alloc::Layout;
use std::any::TypeId;
use std::collections::HashMap;
use crate::ecs::prelude::Archetype;
use super::archegraph::{Archegraph, GraphEntityIndex};
use super::archetype::Archetype;
use super::entity::{Entity, EntityId};
use super::entity_index::EntityIndex;
@ -26,6 +25,11 @@ impl World {
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>();
@ -53,19 +57,33 @@ impl World {
entity
};
let type_id = TypeId::of::<T>();
self.typeid_lookup.insert(type_id, 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
}
pub fn create(&mut self, archetype: &Archetype) -> Entity {
let graph_node = self
.archegraph
.lookup_or_create(archetype, &self.entity_index);
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 = archetable.insert(entry.entity);
entry.update((graph_node.index(), row))
}
@ -84,3 +102,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