Skip to content

Instantly share code, notes, and snippets.

@matthewjberger
Created July 19, 2025 15:01
Show Gist options
  • Select an option

  • Save matthewjberger/35296e570bb1813abe51e8798910f90e to your computer and use it in GitHub Desktop.

Select an option

Save matthewjberger/35296e570bb1813abe51e8798910f90e to your computer and use it in GitHub Desktop.
freecs expansion, showing how the underlying ecs works
use ecs::*;
mod ecs {
use super::components::*;
#[derive(
Default, Clone, Copy, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct Entity {
pub id: u32,
pub generation: u32,
}
impl std::fmt::Display for Entity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { id, generation } = self;
write!(f, "Id: {id} - Generation: {generation}")
}
}
#[allow(unused)]
#[derive(Default, Debug, Clone)]
pub struct EntityBuilder {
position: Option<Position>,
velocity: Option<Velocity>,
health: Option<Health>,
}
#[allow(unused)]
impl EntityBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_position(&mut self, value: Position) -> &mut Self {
self.position = Some(value);
self
}
pub fn with_velocity(&mut self, value: Velocity) -> &mut Self {
self.velocity = Some(value);
self
}
pub fn with_health(&mut self, value: Health) -> &mut Self {
self.health = Some(value);
self
}
pub fn spawn(&self, world: &mut World, instances: usize) -> Vec<Entity> {
let mut mask = 0;
if self.position.is_some() {
mask |= POSITION;
}
if self.velocity.is_some() {
mask |= VELOCITY;
}
if self.health.is_some() {
mask |= HEALTH;
}
let entities = world.spawn_entities(mask, instances);
for entity in entities.iter() {
if let Some(component) = self.position.clone() {
world.set_position(*entity, component);
}
if let Some(component) = self.velocity.clone() {
world.set_velocity(*entity, component);
}
if let Some(component) = self.health.clone() {
world.set_health(*entity, component);
}
}
entities
}
}
#[repr(u64)]
#[allow(clippy::upper_case_acronyms)]
#[allow(non_camel_case_types)]
pub enum Component {
POSITION,
VELOCITY,
HEALTH,
}
pub const POSITION: u64 = 1 << (Component::POSITION as u64);
pub const VELOCITY: u64 = 1 << (Component::VELOCITY as u64);
pub const HEALTH: u64 = 1 << (Component::HEALTH as u64);
pub const COMPONENT_COUNT: usize = {
let mut count = 0;
count += 1;
let _ = Component::POSITION;
count += 1;
let _ = Component::VELOCITY;
count += 1;
let _ = Component::HEALTH;
count
};
#[derive(Default)]
pub struct EntityAllocator {
next_id: u32,
free_ids: Vec<(u32, u32)>,
}
#[derive(Copy, Clone, Default)]
struct EntityLocation {
generation: u32,
table_index: u32,
array_index: u32,
allocated: bool,
}
#[derive(Default)]
pub struct EntityLocations {
locations: Vec<EntityLocation>,
}
#[derive(Default)]
#[allow(unused)]
pub struct World {
entity_locations: EntityLocations,
tables: Vec<ComponentArrays>,
allocator: EntityAllocator,
pub resources: Resources,
table_edges: Vec<TableEdges>,
table_lookup: std::collections::HashMap<u64, usize>,
}
#[allow(unused)]
impl World {
#[inline]
pub fn get_position(&self, entity: Entity) -> Option<&Position> {
let (table_index, array_index) = get_location(&self.entity_locations, entity)?;
if !self.entity_locations.locations[entity.id as usize].allocated {
return None;
}
let table = &self.tables[table_index];
if table.mask & POSITION == 0 {
return None;
}
Some(&table.position[array_index])
}
#[inline]
pub fn get_position_mut(&mut self, entity: Entity) -> Option<&mut Position> {
let (table_index, array_index) = get_location(&self.entity_locations, entity)?;
let table = &mut self.tables[table_index];
if table.mask & POSITION == 0 {
return None;
}
Some(&mut table.position[array_index])
}
#[inline]
pub fn entity_has_position(&self, entity: Entity) -> bool {
self.entity_has_components(entity, POSITION)
}
#[inline]
pub fn set_position(&mut self, entity: Entity, value: Position) {
if let Some((table_index, array_index)) = get_location(&self.entity_locations, entity) {
if self.entity_locations.locations[entity.id as usize].allocated {
let table = &mut self.tables[table_index];
if table.mask & POSITION != 0 {
table.position[array_index] = value;
return;
}
}
}
self.add_components(entity, POSITION);
if let Some((table_index, array_index)) = get_location(&self.entity_locations, entity) {
self.tables[table_index].position[array_index] = value;
}
}
#[inline]
pub fn add_position(&mut self, entity: Entity) {
self.add_components(entity, POSITION);
}
#[inline]
pub fn remove_position(&mut self, entity: Entity) -> bool {
self.remove_components(entity, POSITION)
}
#[inline]
pub fn get_velocity(&self, entity: Entity) -> Option<&Velocity> {
let (table_index, array_index) = get_location(&self.entity_locations, entity)?;
if !self.entity_locations.locations[entity.id as usize].allocated {
return None;
}
let table = &self.tables[table_index];
if table.mask & VELOCITY == 0 {
return None;
}
Some(&table.velocity[array_index])
}
#[inline]
pub fn get_velocity_mut(&mut self, entity: Entity) -> Option<&mut Velocity> {
let (table_index, array_index) = get_location(&self.entity_locations, entity)?;
let table = &mut self.tables[table_index];
if table.mask & VELOCITY == 0 {
return None;
}
Some(&mut table.velocity[array_index])
}
#[inline]
pub fn entity_has_velocity(&self, entity: Entity) -> bool {
self.entity_has_components(entity, VELOCITY)
}
#[inline]
pub fn set_velocity(&mut self, entity: Entity, value: Velocity) {
if let Some((table_index, array_index)) = get_location(&self.entity_locations, entity) {
if self.entity_locations.locations[entity.id as usize].allocated {
let table = &mut self.tables[table_index];
if table.mask & VELOCITY != 0 {
table.velocity[array_index] = value;
return;
}
}
}
self.add_components(entity, VELOCITY);
if let Some((table_index, array_index)) = get_location(&self.entity_locations, entity) {
self.tables[table_index].velocity[array_index] = value;
}
}
#[inline]
pub fn add_velocity(&mut self, entity: Entity) {
self.add_components(entity, VELOCITY);
}
#[inline]
pub fn remove_velocity(&mut self, entity: Entity) -> bool {
self.remove_components(entity, VELOCITY)
}
#[inline]
pub fn get_health(&self, entity: Entity) -> Option<&Health> {
let (table_index, array_index) = get_location(&self.entity_locations, entity)?;
if !self.entity_locations.locations[entity.id as usize].allocated {
return None;
}
let table = &self.tables[table_index];
if table.mask & HEALTH == 0 {
return None;
}
Some(&table.health[array_index])
}
#[inline]
pub fn get_health_mut(&mut self, entity: Entity) -> Option<&mut Health> {
let (table_index, array_index) = get_location(&self.entity_locations, entity)?;
let table = &mut self.tables[table_index];
if table.mask & HEALTH == 0 {
return None;
}
Some(&mut table.health[array_index])
}
#[inline]
pub fn entity_has_health(&self, entity: Entity) -> bool {
self.entity_has_components(entity, HEALTH)
}
#[inline]
pub fn set_health(&mut self, entity: Entity, value: Health) {
if let Some((table_index, array_index)) = get_location(&self.entity_locations, entity) {
if self.entity_locations.locations[entity.id as usize].allocated {
let table = &mut self.tables[table_index];
if table.mask & HEALTH != 0 {
table.health[array_index] = value;
return;
}
}
}
self.add_components(entity, HEALTH);
if let Some((table_index, array_index)) = get_location(&self.entity_locations, entity) {
self.tables[table_index].health[array_index] = value;
}
}
#[inline]
pub fn add_health(&mut self, entity: Entity) {
self.add_components(entity, HEALTH);
}
#[inline]
pub fn remove_health(&mut self, entity: Entity) -> bool {
self.remove_components(entity, HEALTH)
}
pub fn spawn_entities(&mut self, mask: u64, count: usize) -> Vec<Entity> {
let mut entities = Vec::with_capacity(count);
let table_index = get_or_create_table(self, mask);
self.tables[table_index].entity_indices.reserve(count);
if mask & POSITION != 0 {
self.tables[table_index].position.reserve(count);
}
if mask & VELOCITY != 0 {
self.tables[table_index].velocity.reserve(count);
}
if mask & HEALTH != 0 {
self.tables[table_index].health.reserve(count);
}
for _ in 0..count {
let entity = create_entity(self);
add_to_table(
&mut self.tables[table_index],
entity,
(
if mask & POSITION != 0 {
Some(<Position>::default())
} else {
None
},
if mask & VELOCITY != 0 {
Some(<Velocity>::default())
} else {
None
},
if mask & HEALTH != 0 {
Some(<Health>::default())
} else {
None
},
),
);
entities.push(entity);
insert_location(
&mut self.entity_locations,
entity,
(
table_index,
self.tables[table_index].entity_indices.len() - 1,
),
);
}
entities
}
pub fn query_entities(&self, mask: u64) -> Vec<Entity> {
let total_capacity = self
.tables
.iter()
.filter(|table| table.mask & mask == mask)
.map(|table| table.entity_indices.len())
.sum();
let mut result = Vec::with_capacity(total_capacity);
for table in &self.tables {
if table.mask & mask == mask {
result.extend(
table
.entity_indices
.iter()
.copied()
.filter(|&e| self.entity_locations.locations[e.id as usize].allocated),
);
}
}
result
}
pub fn query_first_entity(&self, mask: u64) -> Option<Entity> {
for table in &self.tables {
if !table.mask & mask == mask {
continue;
}
let indices = table
.entity_indices
.iter()
.copied()
.filter(|&e| self.entity_locations.locations[e.id as usize].allocated)
.collect::<Vec<_>>();
if let Some(entity) = indices.first() {
return Some(*entity);
}
}
None
}
pub fn despawn_entities(&mut self, entities: &[Entity]) -> Vec<Entity> {
let mut despawned = Vec::with_capacity(entities.len());
let mut tables_to_update = Vec::new();
for &entity in entities {
let id = entity.id as usize;
if id < self.entity_locations.locations.len() {
let loc = &mut self.entity_locations.locations[id];
if loc.allocated && loc.generation == entity.generation {
let table_idx = loc.table_index as usize;
let array_idx = loc.array_index as usize;
loc.allocated = false;
loc.generation = loc.generation.wrapping_add(1);
self.allocator.free_ids.push((entity.id, loc.generation));
tables_to_update.push((table_idx, array_idx));
despawned.push(entity);
}
}
}
for (table_idx, array_idx) in tables_to_update.into_iter().rev() {
if table_idx >= self.tables.len() {
continue;
}
let table = &mut self.tables[table_idx];
let last_idx = table.entity_indices.len() - 1;
if array_idx < last_idx {
let moved_entity = table.entity_indices[last_idx];
if let Some(loc) = self
.entity_locations
.locations
.get_mut(moved_entity.id as usize)
{
if loc.allocated {
loc.array_index = array_idx as u32;
}
}
}
if table.mask & POSITION != 0 {
table.position.swap_remove(array_idx);
}
if table.mask & VELOCITY != 0 {
table.velocity.swap_remove(array_idx);
}
if table.mask & HEALTH != 0 {
table.health.swap_remove(array_idx);
}
table.entity_indices.swap_remove(array_idx);
}
despawned
}
pub fn add_components(&mut self, entity: Entity, mask: u64) -> bool {
if let Some((table_index, array_index)) = get_location(&self.entity_locations, entity) {
let current_mask = self.tables[table_index].mask;
if current_mask & mask == mask {
return true;
}
let target_table = if mask.count_ones() == 1 {
get_component_index(mask)
.and_then(|idx| self.table_edges[table_index].add_edges[idx])
} else {
None
};
let new_table_index =
target_table.unwrap_or_else(|| get_or_create_table(self, current_mask | mask));
move_entity(self, entity, table_index, array_index, new_table_index);
true
} else {
false
}
}
pub fn remove_components(&mut self, entity: Entity, mask: u64) -> bool {
if let Some((table_index, array_index)) = get_location(&self.entity_locations, entity) {
let current_mask = self.tables[table_index].mask;
if current_mask & mask == 0 {
return true;
}
let target_table = if mask.count_ones() == 1 {
get_component_index(mask)
.and_then(|idx| self.table_edges[table_index].remove_edges[idx])
} else {
None
};
let new_table_index =
target_table.unwrap_or_else(|| get_or_create_table(self, current_mask & !mask));
move_entity(self, entity, table_index, array_index, new_table_index);
true
} else {
false
}
}
pub fn component_mask(&self, entity: Entity) -> Option<u64> {
get_location(&self.entity_locations, entity)
.map(|(table_index, _)| self.tables[table_index].mask)
}
pub fn get_all_entities(&self) -> Vec<Entity> {
let mut result = Vec::new();
for table in &self.tables {
result.extend(
table
.entity_indices
.iter()
.copied()
.filter(|&e| self.entity_locations.locations[e.id as usize].allocated),
);
}
result
}
pub fn entity_has_components(&self, entity: Entity, components: u64) -> bool {
self.component_mask(entity).unwrap_or(0) & components != 0
}
}
#[derive(Default)]
pub struct Resources {
pub delta_time: f32,
}
#[derive(Default)]
pub struct ComponentArrays {
pub position: Vec<Position>,
pub velocity: Vec<Velocity>,
pub health: Vec<Health>,
pub entity_indices: Vec<Entity>,
pub mask: u64,
}
#[derive(Copy, Clone)]
struct TableEdges {
add_edges: [Option<usize>; COMPONENT_COUNT],
remove_edges: [Option<usize>; COMPONENT_COUNT],
}
impl Default for TableEdges {
fn default() -> Self {
Self {
add_edges: [None; COMPONENT_COUNT],
remove_edges: [None; COMPONENT_COUNT],
}
}
}
fn get_component_index(mask: u64) -> Option<usize> {
match mask {
POSITION => Some(Component::POSITION as _),
VELOCITY => Some(Component::VELOCITY as _),
HEALTH => Some(Component::HEALTH as _),
_ => None,
}
}
fn remove_from_table(arrays: &mut ComponentArrays, index: usize) -> Option<Entity> {
let last_index = arrays.entity_indices.len() - 1;
let mut swapped_entity = None;
if index < last_index {
swapped_entity = Some(arrays.entity_indices[last_index]);
}
if arrays.mask & POSITION != 0 {
arrays.position.swap_remove(index);
}
if arrays.mask & VELOCITY != 0 {
arrays.velocity.swap_remove(index);
}
if arrays.mask & HEALTH != 0 {
arrays.health.swap_remove(index);
}
arrays.entity_indices.swap_remove(index);
swapped_entity
}
fn move_entity(
world: &mut World,
entity: Entity,
from_table: usize,
from_index: usize,
to_table: usize,
) {
let components = {
let from_table_ref = &mut world.tables[from_table];
(
if from_table_ref.mask & POSITION != 0 {
Some(std::mem::take(&mut from_table_ref.position[from_index]))
} else {
None
},
if from_table_ref.mask & VELOCITY != 0 {
Some(std::mem::take(&mut from_table_ref.velocity[from_index]))
} else {
None
},
if from_table_ref.mask & HEALTH != 0 {
Some(std::mem::take(&mut from_table_ref.health[from_index]))
} else {
None
},
)
};
add_to_table(&mut world.tables[to_table], entity, components);
let new_index = world.tables[to_table].entity_indices.len() - 1;
insert_location(&mut world.entity_locations, entity, (to_table, new_index));
if let Some(swapped) = remove_from_table(&mut world.tables[from_table], from_index) {
insert_location(
&mut world.entity_locations,
swapped,
(from_table, from_index),
);
}
}
fn get_location(locations: &EntityLocations, entity: Entity) -> Option<(usize, usize)> {
let id = entity.id as usize;
if id >= locations.locations.len() {
return None;
}
let location = &locations.locations[id];
if !location.allocated || location.generation != entity.generation {
return None;
}
Some((location.table_index as usize, location.array_index as usize))
}
fn insert_location(locations: &mut EntityLocations, entity: Entity, location: (usize, usize)) {
let id = entity.id as usize;
if id >= locations.locations.len() {
locations
.locations
.resize(id + 1, EntityLocation::default());
}
locations.locations[id] = EntityLocation {
generation: entity.generation,
table_index: location.0 as u32,
array_index: location.1 as u32,
allocated: true,
};
}
fn create_entity(world: &mut World) -> Entity {
if let Some((id, next_gen)) = world.allocator.free_ids.pop() {
let id_usize = id as usize;
if id_usize >= world.entity_locations.locations.len() {
world.entity_locations.locations.resize(
(world.entity_locations.locations.len() * 2).max(64),
EntityLocation::default(),
);
}
world.entity_locations.locations[id_usize].generation = next_gen;
Entity {
id,
generation: next_gen,
}
} else {
let id = world.allocator.next_id;
world.allocator.next_id += 1;
let id_usize = id as usize;
if id_usize >= world.entity_locations.locations.len() {
world.entity_locations.locations.resize(
(world.entity_locations.locations.len() * 2).max(64),
EntityLocation::default(),
);
}
Entity { id, generation: 0 }
}
}
fn add_to_table(
arrays: &mut ComponentArrays,
entity: Entity,
components: (Option<Position>, Option<Velocity>, Option<Health>),
) {
let (position, velocity, health) = components;
if arrays.mask & POSITION != 0 {
if let Some(component) = position {
arrays.position.push(component);
} else {
arrays.position.push(<Position>::default());
}
}
if arrays.mask & VELOCITY != 0 {
if let Some(component) = velocity {
arrays.velocity.push(component);
} else {
arrays.velocity.push(<Velocity>::default());
}
}
if arrays.mask & HEALTH != 0 {
if let Some(component) = health {
arrays.health.push(component);
} else {
arrays.health.push(<Health>::default());
}
}
arrays.entity_indices.push(entity);
}
fn get_or_create_table(world: &mut World, mask: u64) -> usize {
if let Some(&index) = world.table_lookup.get(&mask) {
return index;
}
let table_index = world.tables.len();
world.tables.push(ComponentArrays {
mask,
..Default::default()
});
world.table_edges.push(TableEdges::default());
world.table_lookup.insert(mask, table_index);
for comp_mask in [POSITION, VELOCITY, HEALTH] {
if let Some(comp_idx) = get_component_index(comp_mask) {
for (idx, table) in world.tables.iter().enumerate() {
if table.mask | comp_mask == mask {
world.table_edges[idx].add_edges[comp_idx] = Some(table_index);
}
if table.mask & !comp_mask == mask {
world.table_edges[idx].remove_edges[comp_idx] = Some(table_index);
}
}
}
}
table_index
}
}
pub fn main() {
let mut world = World::default();
// Spawn entities with components
let _entity = world.spawn_entities(POSITION | VELOCITY, 1)[0];
// Or use the entity builder
let entity = EntityBuilder::new()
.with_position(Position { x: 1.0, y: 2.0 })
.spawn(&mut world, 1)[0];
// Read components using the generated methods
let position = world.get_position(entity);
println!("Position: {:?}", position);
// Set components (adds if not present)
world.set_position(entity, Position { x: 1.0, y: 2.0 });
// Mutate a component
if let Some(position) = world.get_position_mut(entity) {
position.x += 1.0;
}
// Get an entity's component mask
let _component_mask = world.component_mask(entity).unwrap();
// Add a new component to an entity
world.add_components(entity, HEALTH);
// Or use the generated add method
world.add_health(entity);
// Query all entities
let _entities = world.get_all_entities();
// Query all entities with a specific component
let _players = world.query_entities(POSITION | VELOCITY | HEALTH);
// Query the first entity with a specific component,
// returning early instead of checking remaining entities
let _first_player_entity = world.query_first_entity(POSITION | VELOCITY | HEALTH);
// Remove a component from an entity
world.remove_components(entity, HEALTH);
// Or use the generated remove method
world.remove_health(entity);
// Check if entity has components
if world.entity_has_position(entity) {
println!("Entity has position component");
}
// Systems are functions that transform component data
systems::run_systems(&mut world);
// Despawn entities, freeing their table slots for reuse
world.despawn_entities(&[entity]);
}
use components::*;
mod components {
#[derive(Default, Debug, Clone, Copy)]
pub struct Position {
pub x: f32,
pub y: f32,
}
#[derive(Default, Debug, Clone, Copy)]
pub struct Velocity {
pub x: f32,
pub y: f32,
}
#[derive(Default, Debug, Clone, Copy)]
pub struct Health {
pub value: f32,
}
}
mod systems {
use super::*;
pub fn run_systems(world: &mut World) {
// Systems use queries and component accessors
example_system(world);
update_positions_system(world);
health_system(world);
}
fn example_system(world: &mut World) {
for entity in world.query_entities(POSITION | VELOCITY) {
if let Some(position) = world.get_position_mut(entity) {
position.x += 1.0;
}
}
}
fn update_positions_system(world: &mut World) {
let dt = world.resources.delta_time;
// Collect entities with their velocities first to avoid borrow conflicts
let updates: Vec<(Entity, Velocity)> = world
.query_entities(POSITION | VELOCITY)
.into_iter()
.filter_map(|entity| world.get_velocity(entity).map(|vel| (entity, *vel)))
.collect();
// Now update positions
for (entity, vel) in updates {
if let Some(pos) = world.get_position_mut(entity) {
pos.x += vel.x * dt;
pos.y += vel.y * dt;
}
}
}
fn health_system(world: &mut World) {
for entity in world.query_entities(HEALTH) {
if let Some(health) = world.get_health_mut(entity) {
health.value *= 0.98;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment