refactor(ecs)!: reworked the entity id system

This commit is contained in:
lisk77 2025-11-25 23:11:37 +01:00
parent eced6ddf3f
commit 607bf94f1e
7 changed files with 198 additions and 95 deletions

View file

@ -1,5 +1,7 @@
use comet_colors::{Color as ColorTrait, LinearRgba}; 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_input::keyboard::Key;
use comet_log::*; use comet_log::*;
use comet_renderer::renderer::Renderer; use comet_renderer::renderer::Renderer;
@ -163,22 +165,22 @@ impl App {
} }
/// Creates a new entity and returns its ID. /// Creates a new entity and returns its ID.
pub fn new_entity(&mut self) -> usize { pub fn new_entity(&mut self) -> EntityId {
self.scene.new_entity() as usize self.scene.new_entity()
} }
/// Deletes an entity by its ID. /// 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) self.scene.delete_entity(entity_id)
} }
/// Gets an immutable reference to an entity by its 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) self.scene.get_entity(entity_id)
} }
/// Gets a mutable reference to an entity by its 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) 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. /// 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. /// Overwrites the previous component if another component of the same type is added.
pub fn add_component<C: Component>(&mut self, entity_id: usize, component: C) { pub fn add_component<C: Component>(&mut self, entity_id: EntityId, component: C) {
self.scene.add_component(entity_id, component) self.scene.add_component(entity_id, component)
} }
/// Removes a component from an entity by its ID. /// Removes a component from an entity by its ID.
pub fn remove_component<C: Component>(&mut self, entity_id: usize) { pub fn remove_component<C: Component>(&mut self, entity_id: EntityId) {
self.scene.remove_component::<C>(entity_id) self.scene.remove_component::<C>(entity_id)
} }
/// Returns a reference to a component of an entity by its ID. /// Returns a reference to a component of an entity by its ID.
pub fn get_component<C: Component>(&self, entity_id: usize) -> Option<&C> { pub fn get_component<C: Component>(&self, entity_id: EntityId) -> Option<&C> {
self.scene.get_component::<C>(entity_id) self.scene.get_component::<C>(entity_id)
} }
/// Returns a mutable reference to a component of an entity by its ID. /// Returns a mutable reference to a component of an entity by its ID.
pub fn get_component_mut<C: Component>(&mut self, entity_id: usize) -> Option<&mut C> { pub fn get_component_mut<C: Component>(&mut self, entity_id: EntityId) -> Option<&mut C> {
self.scene.get_component_mut::<C>(entity_id) self.scene.get_component_mut::<C>(entity_id)
} }
/// Returns a list of entities that have the given components. /// 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. /// 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. /// Otherwise it would be a factorial complexity chaos.
pub fn get_entities_with(&self, components: Vec<TypeId>) -> Vec<usize> { pub fn get_entities_with(&self, components: Vec<TypeId>) -> Vec<EntityId> {
self.scene.get_entities_with(components) self.scene.get_entities_with(components)
} }
@ -233,7 +235,7 @@ impl App {
} }
/// Returns whether an entity has the given component. /// Returns whether an entity has the given component.
pub fn has<C: Component>(&self, entity_id: usize) -> bool { pub fn has<C: Component>(&self, entity_id: EntityId) -> bool {
self.scene.has::<C>(entity_id) self.scene.has::<C>(entity_id)
} }
@ -243,7 +245,7 @@ impl App {
} }
/// Spawns a prefab with the given name. /// Spawns a prefab with the given name.
pub fn spawn_prefab(&mut self, name: &str) -> Option<usize> { pub fn spawn_prefab(&mut self, name: &str) -> Option<EntityId> {
self.scene.spawn_prefab(name) self.scene.spawn_prefab(name)
} }

View file

@ -436,13 +436,15 @@ impl Camera for Camera2D {
let entities = scene.entities(); let entities = scene.entities();
let mut visible_entities = Vec::new(); let mut visible_entities = Vec::new();
for entity in entities { for entity in entities {
let id = *entity.clone().unwrap().id() as usize; if let Some(ent) = entity.clone() {
if let Some(transform) = scene.get_component::<Transform2D>(id) { let id = ent.id();
if self.in_view_frustum(camera_position, transform.position()) { if let Some(transform) = scene.get_component::<Transform2D>(id) {
visible_entities.push(entity.clone().unwrap()); 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 visible_entities

View file

@ -1,32 +1,46 @@
use bit_set::BitSet; 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)] #[derive(Debug, Clone, PartialEq)]
pub struct Entity { pub struct Entity {
id: u32, id: EntityId,
components: BitSet components: BitSet,
} }
impl Entity { impl Entity {
pub(crate) fn new(id: u32) -> Self { pub(crate) fn new(index: u32, gen: u32) -> Self {
Self { Self {
id, id: EntityId { index, gen },
components: BitSet::new() components: BitSet::new(),
} }
} }
pub fn id(&self) -> &u32 { pub fn id(&self) -> EntityId {
&self.id self.id
} }
pub(crate) fn add_component(&mut self, component_index: usize) { pub(crate) fn add_component(&mut self, component_index: usize) {
self.components.insert(component_index); self.components.insert(component_index);
} }
pub(crate) fn remove_component(&mut self, component_index: usize) { pub(crate) fn remove_component(&mut self, component_index: usize) {
self.components.remove(component_index); self.components.remove(component_index);
} }
pub(crate) fn get_components(&self) -> &BitSet { pub(crate) fn get_components(&self) -> &BitSet {
&self.components &self.components
} }
} }

View file

@ -1,6 +1,6 @@
use comet_structs::FlatMap; 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) struct PrefabManager {
pub(crate) prefabs: FlatMap<String, PrefabFactory>, pub(crate) prefabs: FlatMap<String, PrefabFactory>,
@ -26,8 +26,8 @@ impl PrefabManager {
macro_rules! register_prefab { macro_rules! register_prefab {
($scene:expr, $name:expr, $($component:expr),* $(,)?) => { ($scene:expr, $name:expr, $($component:expr),* $(,)?) => {
{ {
fn prefab_factory(scene: &mut $crate::Scene) -> usize { fn prefab_factory(scene: &mut $crate::Scene) -> $crate::EntityId {
let entity = scene.new_entity() as usize; let entity = scene.new_entity();
$( $(
scene.add_component(entity, $component); scene.add_component(entity, $component);
)* )*

View file

@ -1,6 +1,6 @@
use crate::archetypes::Archetypes; use crate::archetypes::Archetypes;
use crate::prefabs::PrefabManager; use crate::prefabs::PrefabManager;
use crate::{Component, Entity, IdQueue}; use crate::{Component, Entity, EntityId, IdQueue};
use comet_log::*; use comet_log::*;
use comet_structs::*; use comet_structs::*;
use std::any::TypeId; use std::any::TypeId;
@ -8,6 +8,7 @@ use std::any::TypeId;
pub struct Scene { pub struct Scene {
id_queue: IdQueue, id_queue: IdQueue,
next_id: u32, next_id: u32,
generations: Vec<u32>,
entities: Vec<Option<Entity>>, entities: Vec<Option<Entity>>,
components: ComponentStorage, components: ComponentStorage,
archetypes: Archetypes, archetypes: Archetypes,
@ -19,6 +20,7 @@ impl Scene {
Self { Self {
id_queue: IdQueue::new(), id_queue: IdQueue::new(),
next_id: 0, next_id: 0,
generations: Vec::new(),
entities: Vec::new(), entities: Vec::new(),
components: ComponentStorage::new(), components: ComponentStorage::new(),
archetypes: Archetypes::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<Entity>` which contains all the entities in the current Scene. /// Retuns the `Vec` of `Option<Entity>` which contains all the entities in the current Scene.
pub fn entities(&self) -> &Vec<Option<Entity>> { pub fn entities(&self) -> &Vec<Option<Entity>> {
&self.entities &self.entities
} }
/// Creates a new entity and returns its ID. /// Creates a new entity and returns its ID.
pub fn new_entity(&mut self) -> u32 { pub fn new_entity(&mut self) -> EntityId {
let id = self.next_id; let index = self.next_id;
if (self.next_id as usize) >= self.entities.len() { let gen = if (index as usize) >= self.generations.len() {
self.entities.push(Some(Entity::new(self.next_id))); self.generations.push(0);
self.get_next_id(); 0
info!("Created entity! ID: {}", id); } else {
return id; 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(); self.get_next_id();
info!("Created entity! ID: {}", id); info!("Created entity! ID: {} (gen {})", id.index, id.gen);
id id
} }
/// Gets an immutable reference to an entity by its 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.entities.get(entity_id).and_then(|e| e.as_ref()) 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. /// 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.entities.get_mut(entity_id).and_then(|e| e.as_mut()) 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. /// 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.remove_entity_from_archetype(entity_id as u32, self.get_component_set(entity_id)); if !self.is_alive(entity_id) {
self.entities[entity_id] = None; return;
info!("Deleted entity! ID: {}", entity_id);
for (_, value) in self.components.iter_mut() {
value.remove::<u8>(entity_id);
} }
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::<u8>(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(); 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. /// 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. /// Overwrites the previous component if another component of the same type is added.
pub fn add_component<C: Component + 'static>(&mut self, entity_id: usize, component: C) { pub fn add_component<C: Component + 'static>(&mut self, entity_id: EntityId, component: C) {
let old_component_set = self.get_component_set(entity_id); if !self.is_alive(entity_id) {
if !old_component_set.to_vec().is_empty() { error!(
self.remove_entity_from_archetype(entity_id as u32, old_component_set); "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 if let Some(component_index) = self
.components .components
.keys() .keys()
@ -175,36 +222,43 @@ impl Scene {
if let Some(entity) = self.get_entity_mut(entity_id) { if let Some(entity) = self.get_entity_mut(entity_id) {
entity.add_component(component_index); entity.add_component(component_index);
} else { } 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 { } else {
error!( error!(
"Component {} not registered, cannot add to entity {}", "Component {} not registered, cannot add to entity {}",
C::type_name(), 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) { if !self.archetypes.contains_archetype(&new_component_set) {
self.create_archetype(new_component_set.clone()); 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!( info!(
"Added component {} to entity {}!", "Added component {} to entity {}!",
C::type_name(), C::type_name(),
entity_id entity_id.index
); );
} }
pub fn remove_component<C: Component + 'static>(&mut self, entity_id: usize) { pub fn remove_component<C: Component + 'static>(&mut self, entity_id: EntityId) {
let old_component_set = self.get_component_set(entity_id); if !self.is_alive(entity_id) {
self.remove_entity_from_archetype(entity_id as u32, old_component_set); 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::<C>(entity_id); self.components
.remove_component::<C>(entity_id.index as usize);
if let Some(component_index) = self if let Some(component_index) = self
.components .components
.keys() .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 !new_component_set.to_vec().is_empty() {
if !self.archetypes.contains_archetype(&new_component_set) { if !self.archetypes.contains_archetype(&new_component_set) {
self.create_archetype(new_component_set.clone()); 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!( info!(
"Removed component {} from entity {}!", "Removed component {} from entity {}!",
C::type_name(), C::type_name(),
entity_id entity_id.index
); );
} }
/// Returns a reference to a component of an entity by its ID. /// Returns a reference to a component of an entity by its ID.
pub fn get_component<C: Component + 'static>(&self, entity_id: usize) -> Option<&C> { pub fn get_component<C: Component + 'static>(&self, entity_id: EntityId) -> Option<&C> {
self.components.get_component::<C>(entity_id) if !self.is_alive(entity_id) {
return None;
}
self.components
.get_component::<C>(entity_id.index as usize)
} }
pub fn get_component_mut<C: Component + 'static>( pub fn get_component_mut<C: Component + 'static>(
&mut self, &mut self,
entity_id: usize, entity_id: EntityId,
) -> Option<&mut C> { ) -> Option<&mut C> {
self.components.get_component_mut::<C>(entity_id) if !self.is_alive(entity_id) {
return None;
}
self.components
.get_component_mut::<C>(entity_id.index as usize)
} }
pub fn has<C: Component + 'static>(&self, entity_id: usize) -> bool { pub fn has<C: Component + 'static>(&self, entity_id: EntityId) -> bool {
self.components.get_component::<C>(entity_id).is_some() self.is_alive(entity_id)
&& self
.components
.get_component::<C>(entity_id.index as usize)
.is_some()
} }
/// Returns a list of entities that have the given components. /// Returns a list of entities that have the given components.
pub fn get_entities_with(&self, components: Vec<TypeId>) -> Vec<usize> { pub fn get_entities_with(&self, components: Vec<TypeId>) -> Vec<EntityId> {
let component_set = ComponentSet::from_ids(components); let component_set = ComponentSet::from_ids(components);
let mut result = Vec::new(); let mut result = Vec::new();
for archetype_set in self.archetypes.component_sets() { for archetype_set in self.archetypes.component_sets() {
if component_set.is_subset(&archetype_set) { if component_set.is_subset(&archetype_set) {
if let Some(entities) = self.archetypes.get_archetype(&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 .components
.get_two_mut(&C::type_id(), &K::type_id()); .get_two_mut(&C::type_id(), &K::type_id());
let c_opt = c_set.and_then(|set| set.get_mut::<C>(entity)); let idx = entity.index as usize;
let k_opt = k_set.and_then(|set| set.get_mut::<K>(entity)); let c_opt = c_set.and_then(|set| set.get_mut::<C>(idx));
let k_opt = k_set.and_then(|set| set.get_mut::<K>(idx));
if let (Some(c), Some(k)) = (c_opt, k_opt) { if let (Some(c), Some(k)) = (c_opt, k_opt) {
func(c, k); func(c, k);
@ -301,7 +381,7 @@ impl Scene {
} }
/// Spawns a prefab with the given name. /// Spawns a prefab with the given name.
pub fn spawn_prefab(&mut self, name: &str) -> Option<usize> { pub fn spawn_prefab(&mut self, name: &str) -> Option<EntityId> {
if self.prefabs.has_prefab(name) { if self.prefabs.has_prefab(name) {
if let Some(factory) = self.prefabs.prefabs.get(&name.to_string()) { if let Some(factory) = self.prefabs.prefabs.get(&name.to_string()) {
let factory = *factory; // Copy the function pointer let factory = *factory; // Copy the function pointer

View file

@ -18,7 +18,11 @@ impl CameraManager {
self.cameras.get(self.active_camera).unwrap() self.cameras.get(self.active_camera).unwrap()
} }
pub fn update_from_scene(&mut self, scene: &comet_ecs::Scene, camera_entities: Vec<usize>) { pub fn update_from_scene(
&mut self,
scene: &comet_ecs::Scene,
camera_entities: Vec<comet_ecs::EntityId>,
) {
self.cameras.clear(); self.cameras.clear();
let mut cameras_with_priority: Vec<(RenderCamera, u8)> = Vec::new(); let mut cameras_with_priority: Vec<(RenderCamera, u8)> = Vec::new();

View file

@ -62,7 +62,7 @@ pub struct Renderer2D<'a> {
resource_manager: GraphicResourceManager, resource_manager: GraphicResourceManager,
camera_manager: CameraManager, camera_manager: CameraManager,
render_passes: Vec<RenderPass>, render_passes: Vec<RenderPass>,
cached_render_entities: Vec<usize>, cached_render_entities: Vec<comet_ecs::EntityId>,
last_frame_time: std::time::Instant, last_frame_time: std::time::Instant,
delta_time: f32, delta_time: f32,
} }
@ -754,7 +754,8 @@ impl<'a> Renderer2D<'a> {
let mut dirty_sort = self.cached_render_entities.len() != unsorted_entities.len(); let mut dirty_sort = self.cached_render_entities.len() != unsorted_entities.len();
if !dirty_sort { if !dirty_sort {
let unsorted_set: HashSet<usize> = unsorted_entities.iter().copied().collect(); let unsorted_set: HashSet<comet_ecs::EntityId> =
unsorted_entities.iter().copied().collect();
dirty_sort = self dirty_sort = self
.cached_render_entities .cached_render_entities
.iter() .iter()
@ -952,7 +953,7 @@ impl<'a> Renderer2D<'a> {
} }
} }
fn setup_camera(&mut self, scene: &comet_ecs::Scene, cameras: Vec<usize>) { fn setup_camera(&mut self, scene: &comet_ecs::Scene, cameras: Vec<comet_ecs::EntityId>) {
if cameras.is_empty() { if cameras.is_empty() {
return; return;
} }