From 607bf94f1e0bfafde5352c495c592604ce81e75f Mon Sep 17 00:00:00 2001 From: lisk77 Date: Tue, 25 Nov 2025 23:11:37 +0100 Subject: [PATCH] refactor(ecs)!: reworked the entity id system --- crates/comet_app/src/app.rs | 28 ++-- crates/comet_ecs/src/component.rs | 14 +- crates/comet_ecs/src/entity.rs | 54 ++++--- crates/comet_ecs/src/prefabs.rs | 6 +- crates/comet_ecs/src/scene.rs | 178 +++++++++++++++++------- crates/comet_renderer/src/camera.rs | 6 +- crates/comet_renderer/src/renderer2d.rs | 7 +- 7 files changed, 198 insertions(+), 95 deletions(-) diff --git a/crates/comet_app/src/app.rs b/crates/comet_app/src/app.rs index 083aa48..b998b7b 100755 --- a/crates/comet_app/src/app.rs +++ b/crates/comet_app/src/app.rs @@ -1,5 +1,7 @@ use comet_colors::{Color as ColorTrait, LinearRgba}; -use comet_ecs::{Camera2D, Component, Entity, Render2D, Scene, Text, Transform2D, Transform3D}; +use comet_ecs::{ + Camera2D, Component, Entity, EntityId, Render2D, Scene, Text, Transform2D, Transform3D, +}; use comet_input::keyboard::Key; use comet_log::*; use comet_renderer::renderer::Renderer; @@ -163,22 +165,22 @@ impl App { } /// Creates a new entity and returns its ID. - pub fn new_entity(&mut self) -> usize { - self.scene.new_entity() as usize + pub fn new_entity(&mut self) -> EntityId { + self.scene.new_entity() } /// Deletes an entity by its ID. - pub fn delete_entity(&mut self, entity_id: usize) { + pub fn delete_entity(&mut self, entity_id: EntityId) { self.scene.delete_entity(entity_id) } /// Gets an immutable reference to an entity by its ID. - pub fn get_entity(&self, entity_id: usize) -> Option<&Entity> { + pub fn get_entity(&self, entity_id: EntityId) -> Option<&Entity> { self.scene.get_entity(entity_id) } /// Gets a mutable reference to an entity by its ID. - pub fn get_entity_mut(&mut self, entity_id: usize) -> Option<&mut Entity> { + pub fn get_entity_mut(&mut self, entity_id: EntityId) -> Option<&mut Entity> { self.scene.get_entity_mut(entity_id) } @@ -194,29 +196,29 @@ impl App { /// Adds a component to an entity by its ID and an instance of the component. /// Overwrites the previous component if another component of the same type is added. - pub fn add_component(&mut self, entity_id: usize, component: C) { + pub fn add_component(&mut self, entity_id: EntityId, component: C) { self.scene.add_component(entity_id, component) } /// Removes a component from an entity by its ID. - pub fn remove_component(&mut self, entity_id: usize) { + pub fn remove_component(&mut self, entity_id: EntityId) { self.scene.remove_component::(entity_id) } /// Returns a reference to a component of an entity by its ID. - pub fn get_component(&self, entity_id: usize) -> Option<&C> { + pub fn get_component(&self, entity_id: EntityId) -> Option<&C> { self.scene.get_component::(entity_id) } /// Returns a mutable reference to a component of an entity by its ID. - pub fn get_component_mut(&mut self, entity_id: usize) -> Option<&mut C> { + pub fn get_component_mut(&mut self, entity_id: EntityId) -> Option<&mut C> { self.scene.get_component_mut::(entity_id) } /// Returns a list of entities that have the given components. /// The amount of queriable components is limited to 3 such that the `Archetype` creation is more efficient. /// Otherwise it would be a factorial complexity chaos. - pub fn get_entities_with(&self, components: Vec) -> Vec { + pub fn get_entities_with(&self, components: Vec) -> Vec { self.scene.get_entities_with(components) } @@ -233,7 +235,7 @@ impl App { } /// Returns whether an entity has the given component. - pub fn has(&self, entity_id: usize) -> bool { + pub fn has(&self, entity_id: EntityId) -> bool { self.scene.has::(entity_id) } @@ -243,7 +245,7 @@ impl App { } /// Spawns a prefab with the given name. - pub fn spawn_prefab(&mut self, name: &str) -> Option { + pub fn spawn_prefab(&mut self, name: &str) -> Option { self.scene.spawn_prefab(name) } diff --git a/crates/comet_ecs/src/component.rs b/crates/comet_ecs/src/component.rs index ad6dc39..177f31e 100755 --- a/crates/comet_ecs/src/component.rs +++ b/crates/comet_ecs/src/component.rs @@ -436,13 +436,15 @@ impl Camera for Camera2D { let entities = scene.entities(); let mut visible_entities = Vec::new(); for entity in entities { - let id = *entity.clone().unwrap().id() as usize; - if let Some(transform) = scene.get_component::(id) { - if self.in_view_frustum(camera_position, transform.position()) { - visible_entities.push(entity.clone().unwrap()); + if let Some(ent) = entity.clone() { + let id = ent.id(); + if let Some(transform) = scene.get_component::(id) { + if self.in_view_frustum(camera_position, transform.position()) { + visible_entities.push(ent); + } + } else { + error!("Entity {} missing Transform2D", id.index); } - } else { - error!("Entity {} missing Transform2D", id); } } visible_entities diff --git a/crates/comet_ecs/src/entity.rs b/crates/comet_ecs/src/entity.rs index e906d90..f74cb58 100755 --- a/crates/comet_ecs/src/entity.rs +++ b/crates/comet_ecs/src/entity.rs @@ -1,32 +1,46 @@ use bit_set::BitSet; +/// Handle used to reference entities safely. Contains an index into the entity +/// storage and a generation counter to detect stale handles. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct EntityId { + pub index: u32, + pub gen: u32, +} + +impl Default for EntityId { + fn default() -> Self { + Self { index: 0, gen: 0 } + } +} + #[derive(Debug, Clone, PartialEq)] pub struct Entity { - id: u32, - components: BitSet + id: EntityId, + components: BitSet, } impl Entity { - pub(crate) fn new(id: u32) -> Self { - Self { - id, - components: BitSet::new() - } - } + pub(crate) fn new(index: u32, gen: u32) -> Self { + Self { + id: EntityId { index, gen }, + components: BitSet::new(), + } + } - pub fn id(&self) -> &u32 { - &self.id - } + pub fn id(&self) -> EntityId { + self.id + } - pub(crate) fn add_component(&mut self, component_index: usize) { - self.components.insert(component_index); - } + pub(crate) fn add_component(&mut self, component_index: usize) { + self.components.insert(component_index); + } - pub(crate) fn remove_component(&mut self, component_index: usize) { - self.components.remove(component_index); - } + pub(crate) fn remove_component(&mut self, component_index: usize) { + self.components.remove(component_index); + } - pub(crate) fn get_components(&self) -> &BitSet { - &self.components - } + pub(crate) fn get_components(&self) -> &BitSet { + &self.components + } } diff --git a/crates/comet_ecs/src/prefabs.rs b/crates/comet_ecs/src/prefabs.rs index 5fe8698..b6f1e48 100644 --- a/crates/comet_ecs/src/prefabs.rs +++ b/crates/comet_ecs/src/prefabs.rs @@ -1,6 +1,6 @@ use comet_structs::FlatMap; -pub type PrefabFactory = fn(&mut crate::Scene) -> usize; +pub type PrefabFactory = fn(&mut crate::Scene) -> crate::EntityId; pub(crate) struct PrefabManager { pub(crate) prefabs: FlatMap, @@ -26,8 +26,8 @@ impl PrefabManager { macro_rules! register_prefab { ($scene:expr, $name:expr, $($component:expr),* $(,)?) => { { - fn prefab_factory(scene: &mut $crate::Scene) -> usize { - let entity = scene.new_entity() as usize; + fn prefab_factory(scene: &mut $crate::Scene) -> $crate::EntityId { + let entity = scene.new_entity(); $( scene.add_component(entity, $component); )* diff --git a/crates/comet_ecs/src/scene.rs b/crates/comet_ecs/src/scene.rs index 32218c3..7f3530e 100755 --- a/crates/comet_ecs/src/scene.rs +++ b/crates/comet_ecs/src/scene.rs @@ -1,6 +1,6 @@ use crate::archetypes::Archetypes; use crate::prefabs::PrefabManager; -use crate::{Component, Entity, IdQueue}; +use crate::{Component, Entity, EntityId, IdQueue}; use comet_log::*; use comet_structs::*; use std::any::TypeId; @@ -8,6 +8,7 @@ use std::any::TypeId; pub struct Scene { id_queue: IdQueue, next_id: u32, + generations: Vec, entities: Vec>, components: ComponentStorage, archetypes: Archetypes, @@ -19,6 +20,7 @@ impl Scene { Self { id_queue: IdQueue::new(), next_id: 0, + generations: Vec::new(), entities: Vec::new(), components: ComponentStorage::new(), archetypes: Archetypes::new(), @@ -43,45 +45,80 @@ impl Scene { } } + fn is_alive(&self, id: EntityId) -> bool { + self.generations + .get(id.index as usize) + .is_some_and(|g| *g == id.gen) + && self + .entities + .get(id.index as usize) + .is_some_and(|e| e.is_some()) + } + /// Retuns the `Vec` of `Option` which contains all the entities in the current Scene. pub fn entities(&self) -> &Vec> { &self.entities } /// Creates a new entity and returns its ID. - pub fn new_entity(&mut self) -> u32 { - let id = self.next_id; - if (self.next_id as usize) >= self.entities.len() { - self.entities.push(Some(Entity::new(self.next_id))); - self.get_next_id(); - info!("Created entity! ID: {}", id); - return id; + pub fn new_entity(&mut self) -> EntityId { + let index = self.next_id; + let gen = if (index as usize) >= self.generations.len() { + self.generations.push(0); + 0 + } else { + self.generations[index as usize] + }; + + if (index as usize) >= self.entities.len() { + self.entities.push(Some(Entity::new(index, gen))); + } else { + self.entities[index as usize] = Some(Entity::new(index, gen)); } - self.entities[self.next_id as usize] = Some(Entity::new(self.next_id)); + + let id = EntityId { index, gen }; self.get_next_id(); - info!("Created entity! ID: {}", id); + info!("Created entity! ID: {} (gen {})", id.index, id.gen); id } /// Gets an immutable reference to an entity by its ID. - pub fn get_entity(&self, entity_id: usize) -> Option<&Entity> { - self.entities.get(entity_id).and_then(|e| e.as_ref()) + pub fn get_entity(&self, entity_id: EntityId) -> Option<&Entity> { + if !self.is_alive(entity_id) { + return None; + } + self.entities + .get(entity_id.index as usize) + .and_then(|e| e.as_ref()) } /// Gets a mutable reference to an entity by its ID. - pub fn get_entity_mut(&mut self, entity_id: usize) -> Option<&mut Entity> { - self.entities.get_mut(entity_id).and_then(|e| e.as_mut()) + pub fn get_entity_mut(&mut self, entity_id: EntityId) -> Option<&mut Entity> { + if !self.is_alive(entity_id) { + return None; + } + self.entities + .get_mut(entity_id.index as usize) + .and_then(|e| e.as_mut()) } /// Deletes an entity by its ID. - pub fn delete_entity(&mut self, entity_id: usize) { - self.remove_entity_from_archetype(entity_id as u32, self.get_component_set(entity_id)); - self.entities[entity_id] = None; - info!("Deleted entity! ID: {}", entity_id); - for (_, value) in self.components.iter_mut() { - value.remove::(entity_id); + pub fn delete_entity(&mut self, entity_id: EntityId) { + if !self.is_alive(entity_id) { + return; } - self.id_queue.sorted_enqueue(entity_id as u32); + + let idx = entity_id.index as usize; + self.remove_entity_from_archetype(entity_id.index, self.get_component_set(idx)); + self.entities[idx] = None; + info!("Deleted entity! ID: {}", entity_id.index); + for (_, value) in self.components.iter_mut() { + value.remove::(idx); + } + if let Some(gen) = self.generations.get_mut(idx) { + *gen = gen.wrapping_add(1); + } + self.id_queue.sorted_enqueue(entity_id.index); self.get_next_id(); } @@ -159,13 +196,23 @@ impl Scene { /// Adds a component to an entity by its ID and an instance of the component. /// Overwrites the previous component if another component of the same type is added. - pub fn add_component(&mut self, entity_id: usize, component: C) { - let old_component_set = self.get_component_set(entity_id); - if !old_component_set.to_vec().is_empty() { - self.remove_entity_from_archetype(entity_id as u32, old_component_set); + pub fn add_component(&mut self, entity_id: EntityId, component: C) { + if !self.is_alive(entity_id) { + error!( + "Attempted to add component {} to dead entity {}", + C::type_name(), + entity_id.index + ); + return; } - self.components.set_component(entity_id, component); + let old_component_set = self.get_component_set(entity_id.index as usize); + if !old_component_set.to_vec().is_empty() { + self.remove_entity_from_archetype(entity_id.index, old_component_set); + } + + self.components + .set_component(entity_id.index as usize, component); if let Some(component_index) = self .components .keys() @@ -175,36 +222,43 @@ impl Scene { if let Some(entity) = self.get_entity_mut(entity_id) { entity.add_component(component_index); } else { - error!("Attempted to add component to non-existent entity {}", entity_id); + error!( + "Attempted to add component to non-existent entity {}", + entity_id.index + ); } } else { error!( "Component {} not registered, cannot add to entity {}", C::type_name(), - entity_id + entity_id.index ); } - let new_component_set = self.get_component_set(entity_id); + let new_component_set = self.get_component_set(entity_id.index as usize); if !self.archetypes.contains_archetype(&new_component_set) { self.create_archetype(new_component_set.clone()); } - self.add_entity_to_archetype(entity_id as u32, new_component_set); + self.add_entity_to_archetype(entity_id.index, new_component_set); info!( "Added component {} to entity {}!", C::type_name(), - entity_id + entity_id.index ); } - pub fn remove_component(&mut self, entity_id: usize) { - let old_component_set = self.get_component_set(entity_id); - self.remove_entity_from_archetype(entity_id as u32, old_component_set); + pub fn remove_component(&mut self, entity_id: EntityId) { + if !self.is_alive(entity_id) { + return; + } + let old_component_set = self.get_component_set(entity_id.index as usize); + self.remove_entity_from_archetype(entity_id.index, old_component_set); - self.components.remove_component::(entity_id); + self.components + .remove_component::(entity_id.index as usize); if let Some(component_index) = self .components .keys() @@ -216,48 +270,73 @@ impl Scene { } } - let new_component_set = self.get_component_set(entity_id); + let new_component_set = self.get_component_set(entity_id.index as usize); if !new_component_set.to_vec().is_empty() { if !self.archetypes.contains_archetype(&new_component_set) { self.create_archetype(new_component_set.clone()); } - self.add_entity_to_archetype(entity_id as u32, new_component_set); + self.add_entity_to_archetype(entity_id.index, new_component_set); } info!( "Removed component {} from entity {}!", C::type_name(), - entity_id + entity_id.index ); } /// Returns a reference to a component of an entity by its ID. - pub fn get_component(&self, entity_id: usize) -> Option<&C> { - self.components.get_component::(entity_id) + pub fn get_component(&self, entity_id: EntityId) -> Option<&C> { + if !self.is_alive(entity_id) { + return None; + } + self.components + .get_component::(entity_id.index as usize) } pub fn get_component_mut( &mut self, - entity_id: usize, + entity_id: EntityId, ) -> Option<&mut C> { - self.components.get_component_mut::(entity_id) + if !self.is_alive(entity_id) { + return None; + } + self.components + .get_component_mut::(entity_id.index as usize) } - pub fn has(&self, entity_id: usize) -> bool { - self.components.get_component::(entity_id).is_some() + pub fn has(&self, entity_id: EntityId) -> bool { + self.is_alive(entity_id) + && self + .components + .get_component::(entity_id.index as usize) + .is_some() } /// Returns a list of entities that have the given components. - pub fn get_entities_with(&self, components: Vec) -> Vec { + pub fn get_entities_with(&self, components: Vec) -> Vec { let component_set = ComponentSet::from_ids(components); let mut result = Vec::new(); for archetype_set in self.archetypes.component_sets() { if component_set.is_subset(&archetype_set) { if let Some(entities) = self.archetypes.get_archetype(&archetype_set) { - result.extend(entities.iter().map(|x| *x as usize)); + for index in entities.iter() { + if let Some(gen) = self.generations.get(*index as usize) { + if self + .entities + .get(*index as usize) + .is_some_and(|e| e.is_some()) + { + result.push(EntityId { + index: *index, + gen: *gen, + }); + } + } + } } } } @@ -286,8 +365,9 @@ impl Scene { .components .get_two_mut(&C::type_id(), &K::type_id()); - let c_opt = c_set.and_then(|set| set.get_mut::(entity)); - let k_opt = k_set.and_then(|set| set.get_mut::(entity)); + let idx = entity.index as usize; + let c_opt = c_set.and_then(|set| set.get_mut::(idx)); + let k_opt = k_set.and_then(|set| set.get_mut::(idx)); if let (Some(c), Some(k)) = (c_opt, k_opt) { func(c, k); @@ -301,7 +381,7 @@ impl Scene { } /// Spawns a prefab with the given name. - pub fn spawn_prefab(&mut self, name: &str) -> Option { + pub fn spawn_prefab(&mut self, name: &str) -> Option { if self.prefabs.has_prefab(name) { if let Some(factory) = self.prefabs.prefabs.get(&name.to_string()) { let factory = *factory; // Copy the function pointer diff --git a/crates/comet_renderer/src/camera.rs b/crates/comet_renderer/src/camera.rs index 2e70adf..77f3045 100644 --- a/crates/comet_renderer/src/camera.rs +++ b/crates/comet_renderer/src/camera.rs @@ -18,7 +18,11 @@ impl CameraManager { self.cameras.get(self.active_camera).unwrap() } - pub fn update_from_scene(&mut self, scene: &comet_ecs::Scene, camera_entities: Vec) { + pub fn update_from_scene( + &mut self, + scene: &comet_ecs::Scene, + camera_entities: Vec, + ) { self.cameras.clear(); let mut cameras_with_priority: Vec<(RenderCamera, u8)> = Vec::new(); diff --git a/crates/comet_renderer/src/renderer2d.rs b/crates/comet_renderer/src/renderer2d.rs index 1bef6b5..91ffe06 100644 --- a/crates/comet_renderer/src/renderer2d.rs +++ b/crates/comet_renderer/src/renderer2d.rs @@ -62,7 +62,7 @@ pub struct Renderer2D<'a> { resource_manager: GraphicResourceManager, camera_manager: CameraManager, render_passes: Vec, - cached_render_entities: Vec, + cached_render_entities: Vec, last_frame_time: std::time::Instant, delta_time: f32, } @@ -754,7 +754,8 @@ impl<'a> Renderer2D<'a> { let mut dirty_sort = self.cached_render_entities.len() != unsorted_entities.len(); if !dirty_sort { - let unsorted_set: HashSet = unsorted_entities.iter().copied().collect(); + let unsorted_set: HashSet = + unsorted_entities.iter().copied().collect(); dirty_sort = self .cached_render_entities .iter() @@ -952,7 +953,7 @@ impl<'a> Renderer2D<'a> { } } - fn setup_camera(&mut self, scene: &comet_ecs::Scene, cameras: Vec) { + fn setup_camera(&mut self, scene: &comet_ecs::Scene, cameras: Vec) { if cameras.is_empty() { return; }