#[macro_export] macro_rules! world { ( $world:ident { components { $($name:ident: $type:ty => $mask:ident),* $(,)? }$(,)? $resources:ident { $($resource_name:ident: $resource_type:ty),* $(,)? } } ) => { /// Component masks #[repr(u32)] #[allow(clippy::upper_case_acronyms)] #[allow(non_camel_case_types)] pub enum Component { $($mask,)* All, } pub const ALL: u32 = 0; $(pub const $mask: u32 = 1 << (Component::$mask as u32);)* pub const COMPONENT_COUNT: usize = { Component::All as usize }; /// Entity ID, an index into storage and a generation counter to prevent stale references #[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] pub struct EntityId { pub id: u32, pub generation: u32, } impl std::fmt::Display for EntityId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self { id, generation } = self; write!(f, "Id: {id} - Generation: {generation}") } } // Handles allocation and reuse of entity IDs #[derive(Default, serde::Serialize, serde::Deserialize)] pub struct EntityAllocator { next_id: u32, free_ids: Vec<(u32, u32)>, // (id, next_generation) } #[derive(Copy, Clone, Default, serde::Serialize, serde::Deserialize)] struct EntityLocation { generation: u32, table_index: u16, array_index: u16, allocated: bool, } /// Entity location cache for quick access #[derive(Default, serde::Serialize, serde::Deserialize)] pub struct EntityLocations { locations: Vec, } /// A collection of component tables and resources #[derive(Default, serde::Serialize, serde::Deserialize)] pub struct $world { pub entity_locations: EntityLocations, pub tables: Vec, pub allocator: EntityAllocator, #[serde(skip)] #[allow(unused)] pub resources: $resources, table_edges: Vec, pending_despawns: Vec, } /// Resources #[derive(Default)] pub struct $resources { $(pub $resource_name: $resource_type,)* } /// Component Table #[derive(Default, serde::Serialize, serde::Deserialize)] pub struct ComponentArrays { $(pub $name: Vec<$type>,)* pub entity_indices: Vec, pub mask: u32, } #[derive(Copy, Clone, Default, serde::Serialize, serde::Deserialize)] struct TableEdges { add_edges: [Option; COMPONENT_COUNT], remove_edges: [Option; COMPONENT_COUNT], } fn get_component_index(mask: u32) -> Option { match mask { $($mask => Some(Component::$mask as _),)* _ => None, } } /// Spawn a batch of new entities with the same component mask pub fn spawn_entities(world: &mut $world, mask: u32, count: usize) -> Vec { let mut entities = Vec::with_capacity(count); let table_index = get_or_create_table(world, mask); world.tables[table_index].entity_indices.reserve(count); // Reserve space in components $( if mask & $mask != 0 { world.tables[table_index].$name.reserve(count); } )* for _ in 0..count { let entity = create_entity(world); add_to_table( &mut world.tables[table_index], entity, ( $( if mask & $mask != 0 { Some(<$type>::default()) } else { None }, )* ), ); entities.push(entity); location_insert( &mut world.entity_locations, entity, (table_index, world.tables[table_index].entity_indices.len() - 1), ); } entities } /// Query for all entities that match the component mask pub fn query_entities(world: &$world, mask: u32) -> Vec { let total_capacity = world .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 &world.tables { if table.mask & mask == mask { // Only include allocated entities result.extend( table .entity_indices .iter() .copied() .filter(|&e| world.entity_locations.locations[e.id as usize].allocated), ); } } result } /// Query for the first entity that matches the component mask /// Returns as soon as a match is found, instead of running for all entities pub fn query_first_entity(world: &$world, mask: u32) -> Option { for table in &world.tables { if !has_components!(table, mask) { continue; } let indices = table .entity_indices .iter() .copied() .filter(|&e| world.entity_locations.locations[e.id as usize].allocated) .collect::>(); if let Some(entity) = indices.first() { return Some(*entity); } } None } /// Get a specific component for an entity pub fn get_component(world: &$world, entity: EntityId, mask: u32) -> Option<&T> { let (table_index, array_index) = location_get(&world.entity_locations, entity)?; // Early return if entity is despawned if !world.entity_locations.locations[entity.id as usize].allocated { return None; } let table = &world.tables[table_index]; if table.mask & mask == 0 { return None; } $( if mask == $mask && std::any::TypeId::of::() == std::any::TypeId::of::<$type>() { // SAFETY: This operation is safe because: // 1. We verify the component type T exactly matches $type via TypeId // 2. We confirm the table contains this component via mask check // 3. array_index is valid from location_get bounds check // 4. The reference is valid for the lifetime of the return value // because it's tied to the table reference lifetime // 5. No mutable aliases can exist during the shared borrow // 6. The type cast maintains proper alignment as types are identical return Some(unsafe { &*(&table.$name[array_index] as *const $type as *const T) }); } )* None } /// Get a mutable reference to a specific component for an entity pub fn get_component_mut(world: &mut $world, entity: EntityId, mask: u32) -> Option<&mut T> { let (table_index, array_index) = location_get(&world.entity_locations, entity)?; let table = &mut world.tables[table_index]; if table.mask & mask == 0 { return None; } $( if mask == $mask && std::any::TypeId::of::() == std::any::TypeId::of::<$type>() { // SAFETY: This operation is safe because: // 1. We verify the component type T exactly matches $type via TypeId // 2. We confirm the table contains this component via mask check // 3. array_index is valid from location_get bounds check // 4. We have exclusive access through the mutable borrow // 5. The borrow checker ensures no other references exist // 6. The pointer cast is valid as we verified the types are identical // 7. Proper alignment is maintained as the types are the same return Some(unsafe { &mut *(&mut table.$name[array_index] as *mut $type as *mut T) }); } )* None } /// Despawn a batch of entities pub fn despawn_entities(world: &mut $world, entities: &[EntityId]) -> Vec { let mut despawned = Vec::with_capacity(entities.len()); let mut tables_to_update = Vec::new(); // First pass: mark entities as despawned and collect their table locations for &entity in entities { let id = entity.id as usize; if id < world.entity_locations.locations.len() { let loc = &mut world.entity_locations.locations[id]; if loc.allocated && loc.generation == entity.generation { // Get table info before marking as despawned let table_idx = loc.table_index as usize; let array_idx = loc.array_index as usize; // Mark as despawned loc.allocated = false; loc.generation = loc.generation.wrapping_add(1); world.allocator.free_ids.push((entity.id, loc.generation)); // Collect table info for updates tables_to_update.push((table_idx, array_idx)); despawned.push(entity); } } } // Second pass: remove entities from tables in reverse order to maintain indices for (table_idx, array_idx) in tables_to_update.into_iter().rev() { if table_idx >= world.tables.len() { continue; } let table = &mut world.tables[table_idx]; let last_idx = table.entity_indices.len() - 1; // If we're not removing the last element, update the moved entity's location if array_idx < last_idx { let moved_entity = table.entity_indices[last_idx]; if let Some(loc) = world.entity_locations.locations.get_mut(moved_entity.id as usize) { if loc.allocated { loc.array_index = array_idx as u16; } } } // Remove the entity's components $( if table.mask & $mask != 0 { table.$name.swap_remove(array_idx); } )* table.entity_indices.swap_remove(array_idx); } despawned } /// Add components to an entity pub fn add_components(world: &mut $world, entity: EntityId, mask: u32) -> bool { if let Some((table_index, array_index)) = location_get(&world.entity_locations, entity) { let current_mask = world.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| world.table_edges[table_index].add_edges[idx]) } else { None }; let new_table_index = target_table.unwrap_or_else(|| get_or_create_table(world, current_mask | mask)); move_entity(world, entity, table_index, array_index, new_table_index); true } else { false } } /// Remove components from an entity pub fn remove_components(world: &mut $world, entity: EntityId, mask: u32) -> bool { if let Some((table_index, array_index)) = location_get(&world.entity_locations, entity) { let current_mask = world.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| world.table_edges[table_index].remove_edges[idx]) } else { None }; let new_table_index = target_table.unwrap_or_else(|| get_or_create_table(world, current_mask & !mask)); move_entity(world, entity, table_index, array_index, new_table_index); true } else { false } } /// Get the current component mask for an entity pub fn component_mask(world: &$world, entity: EntityId) -> Option { location_get(&world.entity_locations, entity) .map(|(table_index, _)| world.tables[table_index].mask) } /// Convert an old entity ID to its new ID after merging pub fn remap_entity(entity_mapping: &[(EntityId, EntityId)], old_entity: EntityId) -> Option { entity_mapping .iter() .find(|(old, _)| *old == old_entity) .map(|(_, new)| *new) } /// Copy entities from source world to destination world pub fn merge_worlds(dest: &mut $world, source: &$world) -> Vec<(EntityId, EntityId)> { let mut entity_mapping = Vec::with_capacity(query_entities(source, ALL).len()); // First pass: copy all entities and build mapping for source_table in &source.tables { if source_table.entity_indices.is_empty() { continue; } let entities_to_spawn = source_table.entity_indices.len(); let entity_mask = source_table.mask; // Spawn new entities and copy component data let new_entities = spawn_entities(dest, entity_mask, entities_to_spawn); // Record old->new entity mappings for (i, &old_entity) in source_table.entity_indices.iter().enumerate() { entity_mapping.push((old_entity, new_entities[i])); } // Copy component data let index = dest.tables.len() - 1; let dest_table = &mut dest.tables[index]; let start_idx = dest_table.entity_indices.len() - entities_to_spawn; // Copy all components that exist in this table $( if entity_mask & $mask != 0 { for (i, component) in source_table.$name.iter().enumerate() { dest_table.$name[start_idx + i] = component.clone(); } } )* } entity_mapping } /// Update entity references in components after merging pub fn remap_entity_refs( world: &mut $world, entity_mapping: &[(EntityId, EntityId)], mut remap: T ) { for table in &mut world.tables { remap(entity_mapping, table); } } fn remove_from_table(arrays: &mut ComponentArrays, index: usize) -> Option { 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 & $mask != 0 { arrays.$name.swap_remove(index); } )* arrays.entity_indices.swap_remove(index); swapped_entity } fn move_entity( world: &mut $world, entity: EntityId, from_table: usize, from_index: usize, to_table: usize, ) { let components = get_components(&world.tables[from_table], from_index); add_to_table(&mut world.tables[to_table], entity, components); let new_index = world.tables[to_table].entity_indices.len() - 1; location_insert(&mut world.entity_locations, entity, (to_table, new_index)); if let Some(swapped) = remove_from_table(&mut world.tables[from_table], from_index) { location_insert( &mut world.entity_locations, swapped, (from_table, from_index), ); } } fn get_components( arrays: &ComponentArrays, index: usize, ) -> ( $(Option<$type>,)* ) { ( $( if arrays.mask & $mask != 0 { Some(arrays.$name[index].clone()) } else { None }, )* ) } fn location_get(locations: &EntityLocations, entity: EntityId) -> Option<(usize, usize)> { let id = entity.id as usize; if id >= locations.locations.len() { return None; } let location = &locations.locations[id]; // Only return location if entity is allocated AND generation matches if !location.allocated || location.generation != entity.generation { return None; } Some((location.table_index as usize, location.array_index as usize)) } fn location_insert( locations: &mut EntityLocations, entity: EntityId, 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 u16, array_index: location.1 as u16, allocated: true, }; } fn create_entity(world: &mut $world) -> EntityId { 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; EntityId { 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(), ); } EntityId { id, generation: 0 } } } fn add_to_table( arrays: &mut ComponentArrays, entity: EntityId, components: ( $(Option<$type>,)* ), ) { let ($($name,)*) = components; $( if arrays.mask & $mask != 0 { arrays .$name .push($name.unwrap_or_default()); } )* arrays.entity_indices.push(entity); } fn get_or_create_table(world: &mut $world, mask: u32) -> usize { if let Some((index, _)) = world .tables .iter() .enumerate() .find(|(_, t)| t.mask == mask) { return index; } let table_index = world.tables.len(); world.tables.push(ComponentArrays { mask, ..Default::default() }); world.table_edges.push(TableEdges::default()); // Remove table registry updates and only update edges for comp_mask in [ $($mask,)* ] { 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 } }; } #[macro_export] macro_rules! has_components { ($table:expr, $mask:expr) => { $table.mask & $mask == $mask }; }