Initial commit

main
copygirl 1 month ago
commit 5ace8a2b7b
  1. 2
      .gitignore
  2. 9
      Cargo.toml
  3. 23
      README.md
  4. 114
      src/ecs/archetable.rs
  5. 44
      src/ecs/archetype.rs
  6. 125
      src/ecs/component.rs
  7. 272
      src/ecs/entity.rs
  8. 303
      src/ecs/entity_index.rs
  9. 17
      src/ecs/mod.rs
  10. 135
      src/ecs/relation.rs
  11. 3
      src/ecs/utility/mod.rs
  12. 68
      src/ecs/utility/raw_component_vec.rs
  13. 74
      src/ecs/world.rs
  14. 1
      src/lib.rs

2
.gitignore vendored

@ -0,0 +1,2 @@
/target/
/Cargo.lock

@ -0,0 +1,9 @@
[package]
name = "gaemstone"
version = "0.1.0"
edition = "2021"
[dependencies]
aligned-vec = "0.6.1"
bitflags = "2.6.0"
thiserror = "1.0.63"

@ -0,0 +1,23 @@
# gæmstone Game Engine
Similarly to a certain billionare with his obsession with the letter 𝕏, I have
my own issues. More specifically, I want a game engine that allows for content
creation and scripting at runtime, and while in multiplayer, without any
significant interruptions. It doesn't exist, so I have to write my own.
This has been going on for over a decade, and the name "gæmstone" stuck, thus
there's a couple of failed iterations, such as one written in C#. Considering
the lack of professionalism in this readme, and by how long this has been going
on, you can likely tell the chances of any semblance of success are very low.
However I've had a lot of fun writing my own "toy" ECS in Rust, and I just like
sharing my code for others to see, maybe get inspired by, so here you go!
The idea is to have a sufficiently dynamic [Entity Component System (ECS)](ECS)
at the core, inspired by [Flecs], to support both runtime creation and deletion
of components, as well as first-class support for entity relationships. Then
throw in some [wasmtime] for scripting. As for windowing, events, rendering and
such.. who knows? Maybe WebGPU.
[ECS]: https://en.wikipedia.org/wiki/Entity_component_system
[Flecs]: https://github.com/SanderMertens/flecs
[wasmtime]: https://github.com/bytecodealliance/wasmtime

@ -0,0 +1,114 @@
use std::any::TypeId;
use super::archetype::Archetype;
use super::entity::Entity;
use super::world::World;
use super::utility::RawComponentVec;
pub struct Archetable {
archetype: Archetype,
components: Vec<ComponentInfo>,
columns: Vec<Column>,
entities: Vec<Entity>,
}
pub type EntityRow = usize;
struct ComponentInfo {
entity: Entity,
type_id: Option<TypeId>,
column_index: Option<usize>,
}
struct Column {
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(),
}
}
#[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();
// If the entity has `Layout`, add a column for it.
let column_index = if let Some(layout) = layout {
columns.push(Column {
data: RawComponentVec::new(layout),
component_index: components.len(),
});
Some(columns.len() - 1)
} else {
None
};
components.push(ComponentInfo {
entity,
column_index,
type_id,
});
}
Self {
archetype,
components,
columns,
entities: Vec::new(),
}
}
pub fn push(&mut self, entity: Entity) -> EntityRow {
self.entities.push(entity);
let new_len = self.entities.len();
for column in self.columns.iter_mut() {
column.data.resize(new_len);
}
new_len - 1
}
/// 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> {
self.entities.swap_remove(row);
for column in self.columns.iter_mut() {
column.data.swap_remove(row);
}
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 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>() })
}
#[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>() })
}
}

@ -0,0 +1,44 @@
use super::component::Component;
/// Represents a set of components an [`Entity`] may have.
///
/// [`Entity`]: super::entity::Entity
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Archetype {
components: Vec<Component>,
}
impl Archetype {
pub const EMPTY: Archetype = Self {
components: Vec::new(),
};
#[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;
components.sort();
components.dedup();
Self { components }
}
#[inline]
#[must_use]
pub fn components(&self) -> &[Component] {
&self.components
}
#[must_use]
pub fn has(&self, component: impl Into<Component>) -> bool {
self.components.binary_search(&component.into()).is_ok()
}
}
impl<T> FromIterator<T> for Archetype
where
T: Into<Component>,
{
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Self::new(iter)
}
}

@ -0,0 +1,125 @@
/// Components are plain identifiers which represent anything that can be
/// added to an [`Entity`], which includes other entities and [`Relation`]s.
///
/// They are either a "tag component" or a "value component" depending on
/// whether they can hold a value when added to an entity. Components become
/// "value components" if the built-in [`Layout`] component is present on them,
/// which describes how it will be stored inside of [`Archetable`]s.
///
/// [`Entity`]: super::entity::Entity
/// [`Relation`]: super::relation::Relation
/// [`Archetable`]: super::archetable::Archetable
/// [`Layout`]: std::alloc::Layout
#[repr(C)]
#[derive(Copy, Clone, Eq)]
pub struct Component {
low: u32,
high: u32,
}
bitflags::bitflags! {
/// Component flags are an implementation detail, currently only used to
/// determine whether the `Component` represents an `Entity` or `Relation`.
#[repr(C)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct Flags: u32 {
const NONE = 0;
const RELATION = 1 << 31;
}
}
impl Component {
#[inline]
#[must_use]
pub fn flags(&self) -> Flags {
// SAFETY: If component is valid, masking the high bits
// should produce a valid flags value as well.
unsafe { Flags::from_bits_masked(self.high) }
}
#[inline]
#[must_use]
pub fn to_bits(&self) -> u64 {
// SAFETY: Any representation of bits is a valid u64.
unsafe { std::mem::transmute(*self) }
}
// NOTE: Checked `from_bits` is non-trivial to create and would likely
// require referencing `Entity` and `Relation` which we were able
// to avoid so far. Not implementing it for now until necessary.
#[inline]
#[must_use]
pub unsafe fn from_bits_unchecked(bits: u64) -> Self {
std::mem::transmute(bits)
}
}
impl PartialEq for Component {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.to_bits() == other.to_bits()
}
}
impl Ord for Component {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.to_bits().cmp(&other.to_bits())
}
}
impl PartialOrd for Component {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.to_bits().partial_cmp(&other.to_bits())
}
}
impl std::hash::Hash for Component {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.to_bits().hash(state);
}
}
impl Flags {
// NOTE: When changing this be sure to update documentation below.
pub(crate) const MASK: u32 = 0xF0000000;
/// Extracts only the flags from the specified `u32`.
/// No sanity checking to see if it only has known bits set.
#[inline]
#[must_use]
pub unsafe fn from_bits_masked(bits: u32) -> Self {
Self::from_bits_retain(bits & Self::MASK)
}
/// Extracts a `u32` into a 28-bit value (stored as a `u32`) and `Flags`.
/// Returns `None` if the resulting `Flags` would have unknown bits set.
#[must_use]
pub fn unpack(bits: u32) -> Option<(u32, Self)> {
let flags = Self::from_bits(bits & Self::MASK)?;
let value = bits & !Self::MASK;
Some((value, flags))
}
/// Packs a 28-bit value (stored as a `u32`) and `Flags` into a `u32`.
/// Returns `None` if `value` has bits set outside of the valid range.
#[must_use]
pub fn pack(value: u32, flags: Self) -> Option<u32> {
if value & Self::MASK == 0 {
// SAFETY: Manually checked if value does not have flag bits set.
Some(unsafe { Self::pack_unchecked(value, flags) })
} else {
None
}
}
/// Packs a 28-bit value (stored as a `u32`) and `Flags` into a `u32`.
/// No sanity checking to see if `value` has bits set outside of the valid range.
#[must_use]
pub unsafe fn pack_unchecked(value: u32, flags: Self) -> u32 {
value | flags.bits()
}
}

@ -0,0 +1,272 @@
use super::component::{Component, Flags};
use std::num::NonZeroU32;
/// Entities are plain identifiers that represent an object within a [`World`].
///
/// In a world, entities can be created and destroyed, and [`Component`]s can
/// be associated with alive entities, which describe the nature, state or
/// capabilities of the entity as well as its [`Relation`]s to other entities.
///
/// Entities are [`Component`]s themselves. For example an `IsFrozen` entity
/// added to the entity `Alice` means that "Alice is frozen in place". Or a
/// `Position` entity added to her would mean "Alice has a Position". In this
/// case, `Position` is a so-called "value entity" because it holds a value,
/// and `IsFrozen` is a "tag entity" because it does not.
///
/// [`World`]: super::world::World
/// [`Relation`]: super::relation::Relation
#[repr(C)]
#[derive(Copy, Clone, Eq, Debug)]
pub struct Entity {
id: EntityId,
gen: Generation,
}
/// Represents the raw ID of an [`Entity`].
///
/// These IDs can be reused within a [`World`], but only one
/// alive entity with a specific ID may exist at at any time.
///
/// [`World`]: super::world::World
pub type EntityId = NonZeroU32;
/// Represents the generation of an [`Entity`].
///
/// When an entity dies, its `generation` is increased. This way, once the
/// [`EntityId`] is recycled to represent a new entity, you can differenciate
/// it from the previous entity, even though their `id`s are the same.
///
/// Note that generation is not guaranteed to only be increasing, since when
/// dealing with very long running applications it can could potentially wrap
/// back around to 0. This is also the reason why this type does not implement
/// the `Ord` trait.
///
/// Even though it's unlikely that any once dead [`Entity`] value would stay
/// around long enough to suddenly be considered alive again, avoid storing
/// them in components and instead rely on [`Relation`]s and their automatic
/// clean-up functionalty.
///
/// [`Relation`]: super::relation::Relation
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct Generation(u32);
impl Entity {
#[inline]
#[must_use]
pub fn new(id: EntityId, gen: Generation) -> Self {
Self { id, gen }
}
#[must_use]
pub fn new_checked(id: u32, gen: u32) -> Option<Self> {
let id = EntityId::new(id)?;
let gen = Generation::new(gen)?;
Some(Entity::new(id, gen))
}
#[inline]
#[must_use]
pub const unsafe fn new_unchecked(id: u32, gen: u32) -> Self {
let id = EntityId::new_unchecked(id);
let gen = Generation::new_unchecked(gen);
Self { id, gen }
}
#[inline]
#[must_use]
pub fn id(&self) -> EntityId {
self.id
}
#[inline]
#[must_use]
pub fn generation(&self) -> Generation {
self.gen
}
#[must_use]
pub fn inc_generation(&self) -> Entity {
Self::new(self.id, self.gen.inc())
}
#[inline]
#[must_use]
pub fn to_bits(&self) -> u64 {
// SAFETY: Any representation of bits is a valid u64.
unsafe { std::mem::transmute(*self) }
}
#[inline]
#[must_use]
pub fn from_bits(bits: u64) -> Option<Self> {
let id = bits as u32;
let gen = (bits >> 32) as u32;
Self::new_checked(id, gen)
}
#[inline]
#[must_use]
pub unsafe fn from_bits_unchecked(bits: u64) -> Self {
std::mem::transmute(bits)
}
}
impl TryFrom<Component> for Entity {
type Error = ();
#[inline]
fn try_from(component: Component) -> Result<Self, Self::Error> {
Self::from_bits(component.to_bits()).ok_or(())
}
}
impl From<Entity> for Component {
#[inline]
fn from(entity: Entity) -> Self {
// SAFETY: A valid entity is always a valid component.
unsafe { Component::from_bits_unchecked(entity.to_bits()) }
}
}
impl PartialEq for Entity {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.to_bits() == other.to_bits()
}
}
impl Ord for Entity {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.to_bits().cmp(&other.to_bits())
}
}
impl PartialOrd for Entity {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.to_bits().partial_cmp(&other.to_bits())
}
}
impl std::hash::Hash for Entity {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.to_bits().hash(state);
}
}
impl Generation {
pub(crate) const MASK: u32 = !Flags::MASK;
pub const MIN: Generation = unsafe { Self::new_unchecked(0) };
pub const MAX: Generation = unsafe { Self::new_unchecked(Self::MASK) };
#[must_use]
pub fn new(gen: u32) -> Option<Self> {
if Self::is_valid(gen) {
Some(Self(gen))
} else {
None
}
}
#[inline]
#[must_use]
pub const unsafe fn new_unchecked(gen: u32) -> Self {
Self(gen)
}
#[inline]
#[must_use]
pub fn inc(&self) -> Self {
// SAFETY: Manually ensures that value is in range. Wraps on overflow.
unsafe { Self::new_unchecked((self.0 + 1) & Self::MASK) }
}
#[inline]
#[must_use]
pub fn is_valid(gen: u32) -> bool {
gen & !Self::MASK == 0
}
#[inline]
#[must_use]
pub fn get(&self) -> u32 {
self.0
}
}
impl TryFrom<u32> for Generation {
type Error = ();
#[inline]
fn try_from(gen: u32) -> Result<Self, Self::Error> {
Self::new(gen).ok_or(())
}
}
impl From<Generation> for u32 {
#[inline]
fn from(gen: Generation) -> Self {
gen.get()
}
}
impl std::fmt::Display for Generation {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn entity_new() {
let id = EntityId::new(100).unwrap();
let gen = Generation::new(20).unwrap();
let entity = Entity::new(id, gen);
assert_eq!((100, 20), (entity.id().get(), entity.generation().get()));
}
#[test]
fn entity_new_checked() {
let valid = Entity::new_checked(100, 20);
assert!(valid.is_some());
let invalid_id = Entity::new_checked(0, 20);
let invalid_generation = Entity::new_checked(100, 0x1000_0000);
assert!(invalid_id.is_none());
assert!(invalid_generation.is_none());
}
#[test]
fn entity_inc_generation() {
let old = Entity::new_checked(100, 20).unwrap();
let new = old.inc_generation().inc_generation();
assert_eq!(22, new.generation().get());
// Once generation hits the maximum value, it wraps around.
let really_high_gen = Entity::new(EntityId::MIN, Generation::MAX);
let wrapped_around_gen = really_high_gen.inc_generation();
assert_eq!(0, wrapped_around_gen.generation().get());
// Also ensure no other bits have been touched.
assert_eq!(1, wrapped_around_gen.to_bits());
}
#[test]
fn entity_to_and_from_bits() {
let entity = Entity::new_checked(100, 20).unwrap();
let bits = entity.to_bits();
assert_eq!(0x0_0000014_00000064, bits);
// ^^^^^^^^- id (32 bits)
// ^^^^^^^---------- generation (28 bits)
// ^------------------ flags (4 bits)
// Converting back to bits should yield the same entity value.
let entity_from_bits = Entity::from_bits(bits).unwrap();
assert_eq!(entity, entity_from_bits);
}
}

@ -0,0 +1,303 @@
use thiserror::Error;
use super::entity::{Entity, EntityId, Generation};
/// Internal data structure that keeps track of alive [entities](Entity),
/// generation count, and is used to create, destroy and recycle entities.
///
/// Each alive entity is also associated with a custom value of type `T` that
/// is used to store its [`Archetype`] and the `row` into a [`Archetable`] in
/// which the entity's components are stored. This is also the canonical way to
/// look up an entity by its [`EntityId`], or find out which components it has
/// to potentially access them.
///
/// # Example
///
/// ```
/// use gaemstone::ecs::entity_index::{EntityIndex, DestroyError};
/// use gaemstone::ecs::entity::{Entity, Generation};
///
/// let mut entity_index = EntityIndex::<usize>::new();
///
/// let entity_a = entity_index.create().entity;
/// // Update the data associated with the entity.
/// *entity_index.get_mut(entity_a.id()).unwrap().data = 32;
/// // Simpler way to update the data after creation.
/// let entity_b = entity_index.create().update(64);
///
/// assert_eq!(2, entity_index.alive_count());
/// assert_eq!(Entity::new_checked(1, 0).unwrap(), entity_a);
/// assert_eq!(Entity::new_checked(2, 0).unwrap(), entity_b);
/// assert_eq!(32, entity_index.get(entity_a.id()).unwrap().data);
/// assert_eq!(64, entity_index.get(entity_b.id()).unwrap().data);
///
/// // Destroying an entity returns the associated value.
/// assert_eq!(Ok(64), entity_index.destroy(entity_b));
/// assert_eq!(1, entity_index.alive_count());
///
/// // You can also destroy by `EntityId`, ignoring the generation.
/// assert_eq!(Ok(32), entity_index.destroy_at(entity_a.id()));
/// assert_eq!(0, entity_index.alive_count());
///
/// // Dead entities automatically get recycled.
/// let entity_c = entity_index.create().entity;
/// assert_eq!(entity_a.id(), entity_c.id());
/// assert_eq!(entity_a.generation().inc(), entity_c.generation());
///
/// // Attempting to destroy a dead entity (even if recycled) returns an error.
/// let mismatch = DestroyError::GenerationMismatch { found: entity_c, expected: entity_a.generation() };
/// assert_eq!(Err(mismatch), entity_index.destroy(entity_a));
/// ```
///
/// [`Archetype`]: super::archetype::Archetype
/// [`Archetable`]: super::archetable::Archetable
pub struct EntityIndex<T: Copy + Default> {
dense: Vec<Entity>,
alive_count: usize,
sparse: Vec<SparseEntry<T>>, // TODO: Use a paginated data structure.
}
impl<T: Copy + Default> EntityIndex<T> {
#[must_use]
pub fn new() -> Self {
Self {
dense: Vec::new(),
alive_count: 0,
sparse: Vec::new(),
}
}
/// Returns the number of currently alive entities.
#[inline]
#[must_use]
pub fn alive_count(&self) -> usize {
self.alive_count
}
/// Returns a slice of the currently alive entities.
#[inline]
#[must_use]
pub fn entities(&self) -> &[Entity] {
&self.dense[0..self.alive_count]
}
/// Returns whether the specified [`Entity`] is alive.
#[must_use]
pub fn is_alive(&self, entity: Entity) -> bool {
self.sparse
.get((entity.id().get() - 1) as usize)
.filter(|entry| entry.state == SparseEntryState::Alive)
.map(|entry| self.dense[entry.dense_index])
.map(|found| found.generation() == entity.generation())
.unwrap_or(false)
}
/// Returns whether an entity with the specified [`EntityId`] is alive.
#[must_use]
pub fn is_alive_at(&self, id: EntityId) -> bool {
self.sparse
.get((id.get() - 1) as usize)
.map(|entry| entry.state == SparseEntryState::Alive)
.unwrap_or(false)
}
/// Attempts to get the [`Entry`] for the alive entity with the specified
/// [`EntityId`], which allows for inspecting the [`Entity`] and the data
/// associated with it.
#[must_use]
pub fn get(&self, id: EntityId) -> Option<Entry<T>> {
let sparse_index = (id.get() - 1) as usize;
self.sparse.get(sparse_index).map(|entry| Entry {
entity: self.dense[entry.dense_index],
data: entry.data,
})
}
/// Attempts to get an [`EntryMut`] for the alive entity with the specified
/// [`EntityId`], which allows for inspecting the [`Entity`], and mutable
/// access to the data associated with it.
#[must_use]
pub fn get_mut(&mut self, id: EntityId) -> Option<EntryMut<T>> {
let sparse_index = (id.get() - 1) as usize;
self.sparse.get_mut(sparse_index).map(|entry| EntryMut {
entity: self.dense[entry.dense_index],
data: &mut entry.data,
})
}
/// Creates a new [`Entity`], by either reusing an [`EntityId`] or using a
/// fresh one, and returns an [`EntryMut`] that allows for inspecting the
/// `Entity` value created, as well as mutable access to its data.
#[inline]
pub fn create(&mut self) -> EntryMut<T> {
self.lookup_or_create(self.next_free_entity_id())
}
/// Looks up an [`Entity`] with the specified [`EntityId`], creating it if
/// necessary, and returns an [`EntryMut`] that allows for inspecting the
/// `Entity` value, as well as mutable access to its data.
pub fn lookup_or_create(&mut self, id: EntityId) -> EntryMut<T> {
let sparse_index = (id.get() - 1) as usize;
if sparse_index >= self.sparse.len() {
// Grow `sparse` vector to be able to fit `id`.
self.sparse.resize(sparse_index + 1, Default::default());
}
let entry = &mut self.sparse[sparse_index];
if entry.state == SparseEntryState::Unused {
// `dense_index` is unset, `dense` needs to store a new entity.
entry.dense_index = self.dense.len();
// Create a fresh entity with generation 0.
let entity = Entity::new(id, Generation::MIN);
self.dense.push(entity);
}
// Make some copies so we satisfy the borrow checker.
let dense_index = entry.dense_index;
let entity = self.dense[dense_index];
if entry.state != SparseEntryState::Alive {
entry.state = SparseEntryState::Alive;
// Swap the created / recycled entity into the alive entity range.
self.swap_dense(dense_index, self.alive_count);
self.alive_count += 1;
}
// Data should be `Default` even after recycled,
// as it gets reset when the entity is destroyed.
let data = &mut self.sparse[sparse_index].data;
EntryMut { entity, data }
}
/// Destroys the specified alive entity.
#[inline]
pub fn destroy(&mut self, entity: Entity) -> Result<T, DestroyError> {
self.destroy_at_options(entity.id(), Some(entity.generation()))
}
/// Destroys the alive entity with the specified [EntityId].
#[inline]
pub fn destroy_at(&mut self, id: EntityId) -> Result<T, DestroyError> {
self.destroy_at_options(id, None)
}
}
pub struct Entry<T> {
pub entity: Entity,
pub data: T,
}
pub struct EntryMut<'a, T> {
pub entity: Entity,
pub data: &'a mut T,
}
impl<T> EntryMut<'_, T> {
/// Updates the entry's `data`, returning its `entity`, consuming it in the process.
pub fn update(self, value: T) -> Entity {
*self.data = value;
self.entity
}
}
#[derive(Error, Debug, Eq, PartialEq)]
pub enum DestroyError {
#[error("entity with id `{id}` not found")]
EntityNotFound { id: EntityId },
#[error("entity with id `{id}` not alive")]
EntityNotAlive { id: EntityId },
#[error("entity `{found:?}` found, expected generation `{expected}`")]
GenerationMismatch { found: Entity, expected: Generation },
}
impl<T: Copy + Default> EntityIndex<T> {
fn destroy_at_options(
&mut self,
id: EntityId,
gen: Option<Generation>,
) -> Result<T, DestroyError> {
let sparse_index = (id.get() - 1) as usize;
let Some(entry) = self.sparse.get_mut(sparse_index) else {
return Err(DestroyError::EntityNotFound { id });
};
match entry.state {
SparseEntryState::Unused => return Err(DestroyError::EntityNotFound { id }),
SparseEntryState::Dead => return Err(DestroyError::EntityNotAlive { id }),
_ => {}
};
let dense_index = entry.dense_index;
let entity = &mut self.dense[dense_index];
if let Some(expected) = gen {
if expected != entity.generation() {
return Err(DestroyError::GenerationMismatch {
found: *entity,
expected,
});
}
}
let previous = entry.data;
entry.state = SparseEntryState::Dead;
entry.data = Default::default();
// Increase the generation of the destroyed entity.
*entity = entity.inc_generation();
// Swap the destroyed entity into the recycled entity range.
self.swap_dense(dense_index, self.alive_count - 1);
self.alive_count -= 1;
Ok(previous)
}
/// Returns the next available entity ID, either using that
/// of a dead entity to be recycled a completely fresh one.
fn next_free_entity_id(&self) -> EntityId {
if self.dense.len() > self.alive_count {
self.dense[self.alive_count].id()
} else {
let entity_id = (self.alive_count + 1) as u32;
// SAFETY: ID is guaranteed to be greater than 0.
unsafe { EntityId::new_unchecked(entity_id) }
}
}
fn swap_dense(&mut self, dense_index_a: usize, dense_index_b: usize) {
if dense_index_a == dense_index_b {
return;
}
// This assumes the dense indices are valid.
let entity_a = self.dense[dense_index_a];
let entity_b = self.dense[dense_index_b];
let sparse_index_a = (entity_a.id().get() - 1) as usize;
let sparse_index_b = (entity_b.id().get() - 1) as usize;
// Swap entries in `dense` vector.
self.dense.swap(dense_index_a, dense_index_b);
// Swap indices pointing to `dense` in `sparse` vector.
self.sparse[sparse_index_a].dense_index = dense_index_b;
self.sparse[sparse_index_b].dense_index = dense_index_a;
}
}
#[derive(Copy, Clone, Default)]
struct SparseEntry<T> {
state: SparseEntryState,
dense_index: usize,
data: T,
}
#[derive(Copy, Clone, Default, Eq, PartialEq)]
enum SparseEntryState {
/// Entry hasn't been populated yet.
#[default]
Unused,
/// Entry contains a dead [`Entity`] that may be recycled.
Dead,
/// Entry contains an alive [`Entity`].
Alive,
}

@ -0,0 +1,17 @@
pub mod archetable;
pub mod archetype;
pub mod component;
pub mod entity;
pub mod entity_index;
pub mod relation;
pub mod world;
pub(crate) mod utility;
pub mod prelude {
pub use super::archetype::Archetype;
pub use super::component::Component;
pub use super::entity::Entity;
pub use super::relation::Relation;
pub use super::world::World;
}

@ -0,0 +1,135 @@
use super::component::{Component, Flags};
use super::entity::EntityId;
/// Represents a relation of one [`Entity`] to another.
///
/// Relations are made up like so:
/// - **source**: The entity this relation is added to as a component.
/// - **target**: The target entity of this relation.
/// - **kind**: An entity representing the kind of relation.
///
/// For example, to describe the relation "Alice likes Bob", you would need
/// the entities `Alice` (source) and `Bob` (target), but also a third entity
/// `Likes` (kind). You then add the relation `(Likes, Bob)` to `Alice`.
///
/// Note that relations by default are neither symmetric (so `Bob` might not
/// like `Alice` back) nor exclusive (so `Alice` might also like `Chloe`),
/// however this behavior can be adjusted by adding the `Symmetric` and
/// `Exclusive` tags to the relation kind entity.
///
/// Being a [`Component`], relations can hold values when added to entities,
/// making them a so-called "value relation". The opposite, a relation without
/// value, is called a "tag relation". See also the documentation for the
/// [`Component`] type.
/// **TODO:** Describe the mechanism through which relations decide whether to
/// use either `kind` or `target` for the type of the value held.
/// (That is, once such a mechanism actually exists.)
///
/// [`Entity`]: super::entity::Entity
#[repr(C)]
#[derive(Copy, Clone, Eq)]
pub struct Relation {
target: EntityId,
high: u32,
}
impl Relation {
#[must_use]
pub fn new(kind: EntityId, target: EntityId) -> Option<Self> {
let high = Flags::pack(kind.get(), Flags::RELATION)?;
Some(Self { target, high })
}
#[must_use]
pub fn new_checked(kind: u32, target: u32) -> Option<Self> {
let high = Flags::pack(kind, Flags::RELATION)?;
let target = EntityId::new(target)?;
Some(Self { target, high })
}
#[inline]
#[must_use]
pub unsafe fn new_unchecked(kind: u32, target: u32) -> Self {
let high = Flags::pack_unchecked(kind, Flags::RELATION);
let target = EntityId::new_unchecked(target);
Self { target, high }
}
#[inline]
#[must_use]
pub fn kind(&self) -> EntityId {
// SAFETY: Valid relations will always have valid relation kind.
unsafe { EntityId::new_unchecked(self.high & !Flags::MASK) }
}
#[inline]
#[must_use]
pub fn target(&self) -> EntityId {
self.target
}
#[inline]
#[must_use]
pub fn to_bits(&self) -> u64 {
// SAFETY: Any representation of bits is a valid u64.
unsafe { std::mem::transmute(*self) }
}
#[inline]
#[must_use]
pub fn from_bits(bits: u64) -> Option<Self> {
let kind = (bits >> 32) as u32;
let target = bits as u32;
Self::new_checked(kind, target)
}
#[inline]
#[must_use]
pub unsafe fn from_bits_unchecked(bits: u64) -> Self {
std::mem::transmute(bits)
}
}
impl TryFrom<Component> for Relation {
type Error = ();
#[inline]
fn try_from(component: Component) -> Result<Self, Self::Error> {
Self::from_bits(component.to_bits()).ok_or(())
}
}
impl From<Relation> for Component {
#[inline]
fn from(relation: Relation) -> Self {
// SAFETY: A valid relation is always a valid component.
unsafe { Component::from_bits_unchecked(relation.to_bits()) }
}
}
impl PartialEq for Relation {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.to_bits() == other.to_bits()
}
}
impl Ord for Relation {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.to_bits().cmp(&other.to_bits())
}
}
impl PartialOrd for Relation {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.to_bits().partial_cmp(&other.to_bits())
}
}
impl std::hash::Hash for Relation {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.to_bits().hash(state);
}
}

@ -0,0 +1,3 @@
mod raw_component_vec;
pub use raw_component_vec::RawComponentVec;

@ -0,0 +1,68 @@
use aligned_vec::{AVec, RuntimeAlign};
use std::alloc::Layout;
#[derive(Clone, Debug)]
pub struct RawComponentVec {
layout: Layout,
vec: AVec<u8, RuntimeAlign>,
}
impl RawComponentVec {
#[must_use]
pub fn new(layout: Layout) -> Self {
assert_ne!(0, layout.size());
let vec = AVec::new(layout.align());
Self { layout, vec }
}
#[inline]
#[must_use]
pub fn layout(&self) -> Layout {
self.layout
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.vec.len() / self.layout.size()
}
pub fn resize(&mut self, new_len: usize) {
self.vec.resize(new_len * self.layout.size(), 0);
}
pub fn swap_remove(&mut self, index: usize) {
let size = self.layout.size();
let vec_index = index * size;
let vec_len = self.vec.len();
assert!(vec_index >= vec_len);
let new_vec_len = vec_len - size;
unsafe {
let base_ptr = self.vec.as_mut_ptr();
std::ptr::copy(base_ptr.add(new_vec_len), base_ptr.add(vec_index), size);
self.vec.set_len(new_vec_len);
}
}
pub unsafe fn as_typed_slice<T>(&self) -> &[T] {
// SAFETY: Assuming `T` is correct, pointer should be aligned correctly.
unsafe { core::slice::from_raw_parts(self.as_ptr() as *const T, self.len()) }
}
pub unsafe fn as_typed_slice_mut<T>(&mut self) -> &mut [T] {
// SAFETY: Assuming `T` is correct, pointer should be aligned correctly.
unsafe { core::slice::from_raw_parts_mut(self.as_ptr() as *mut T, self.len()) }
}
#[inline]
#[must_use]
pub fn as_ptr(&self) -> *const u8 {
self.vec.as_ptr()
}
#[inline]
#[must_use]
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.vec.as_mut_ptr()
}
}

@ -0,0 +1,74 @@
use std::alloc::Layout;
use std::any::TypeId;
use std::collections::HashMap;
use super::archetable::Archetable;
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)>,
}
impl World {
#[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(),
};
world.register_component::<Layout>();
world.register_component::<TypeId>();
world
}
#[must_use]
pub fn lookup_alive(&self, id: EntityId) -> Option<Entity> {
self.entity_index.get(id).map(|e| e.entity)
}
#[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()
}
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);
entity
}
#[must_use]
pub fn create(&mut self) -> Entity {
let entry = self.entity_index.create();
let row = self.empty_archetable.push(entry.entity);
entry.update(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) {
// Assume that `swapped` we got from the archetype table is alive.
self.entity_index.get(swapped.id()).unwrap().data = row;
}
true
} else {
false
}
}
}

@ -0,0 +1 @@
pub mod ecs;
Loading…
Cancel
Save