diff options
author | HampusM <hampus@hampusmat.com> | 2025-02-06 13:38:46 +0100 |
---|---|---|
committer | HampusM <hampus@hampusmat.com> | 2025-02-12 21:29:41 +0100 |
commit | 64eddc633cea0f4bc5603cc2d4c4c6eafac5c177 (patch) | |
tree | 3248da82180c4307f4b7ca2a71c16dec32fb8cb3 | |
parent | 2a8718f7c671ab1fc5e38340b467e2bd77f16cc0 (diff) |
-rw-r--r-- | ecs/src/archetype.rs | 56 | ||||
-rw-r--r-- | ecs/src/component/storage.rs | 777 | ||||
-rw-r--r-- | ecs/src/component/storage/archetype.rs | 216 | ||||
-rw-r--r-- | ecs/src/component/storage/graph.rs | 269 | ||||
-rw-r--r-- | ecs/src/lib.rs | 141 | ||||
-rw-r--r-- | ecs/src/query/flexible.rs | 15 | ||||
-rw-r--r-- | ecs/src/relationship.rs | 10 | ||||
-rw-r--r-- | ecs/src/util.rs | 33 |
8 files changed, 871 insertions, 646 deletions
diff --git a/ecs/src/archetype.rs b/ecs/src/archetype.rs deleted file mode 100644 index 846e231..0000000 --- a/ecs/src/archetype.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::hash::{DefaultHasher, Hash, Hasher}; - -use crate::component::Metadata as ComponentMetadata; - -/// Archetype ID. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct Id -{ - hash: u64, -} - -impl Id -{ - pub fn from_components_metadata( - components_metadata: &impl AsRef<[ComponentMetadata]>, - ) -> Self - { - if components_metadata.as_ref().len() == 0 { - return Self { hash: 0 }; - } - - debug_assert!( - components_metadata - .as_ref() - .is_sorted_by_key(|comp_metadata| comp_metadata.id), - "Cannot create archetype ID from a unsorted component metadata list" - ); - - let component_ids = - components_metadata - .as_ref() - .iter() - .filter_map(|component_metadata| { - if component_metadata.is_optional { - return None; - } - - Some(component_metadata.id) - }); - - let mut hasher = DefaultHasher::new(); - - for component_id in component_ids { - component_id.hash(&mut hasher); - } - - let hash = hasher.finish(); - - assert_ne!( - hash, 0, - "Archetype ID 0 is reserved for a archetype with zero components" - ); - - Self { hash } - } -} diff --git a/ecs/src/component/storage.rs b/ecs/src/component/storage.rs index 0475bf1..0e1a03d 100644 --- a/ecs/src/component/storage.rs +++ b/ecs/src/component/storage.rs @@ -1,22 +1,31 @@ use std::any::type_name; -use std::borrow::Borrow; -use std::cell::RefCell; -use std::slice::Iter as SliceIter; -use std::vec::IntoIter as OwnedVecIter; +use std::iter::{Chain, Flatten}; use hashbrown::{HashMap, HashSet}; -use crate::archetype::Id as ArchetypeId; +use crate::component::storage::archetype::{ + Archetype, + ArchetypeEntity, + Id as ArchetypeId, +}; +use crate::component::storage::graph::{ + ArchetypeAddEdgeRecursiveIter, + ArchetypeEdges, + Graph, +}; use crate::component::{Component, Metadata as ComponentMetadata}; use crate::type_name::TypeName; -use crate::uid::Uid; +use crate::uid::{Kind as UidKind, Uid}; use crate::EntityComponent; +pub mod archetype; + +mod graph; + #[derive(Debug, Default)] pub struct Storage { - archetypes: Vec<Archetype>, - archetype_lookup: RefCell<HashMap<ArchetypeId, ArchetypeLookupEntry>>, + graph: Graph, entity_archetype_lookup: HashMap<Uid, ArchetypeId>, } @@ -24,481 +33,280 @@ impl Storage { pub fn iter_archetypes_with_comps( &self, - comp_metadata: impl AsRef<[ComponentMetadata]>, + component_metadata: impl AsRef<[ComponentMetadata]>, ) -> ArchetypeRefIter<'_> { - debug_assert!(comp_metadata - .as_ref() - .is_sorted_by_key(|metadata| metadata.id)); - - let archetype_id = ArchetypeId::from_components_metadata(&comp_metadata); + let archetype_id = ArchetypeId::from_components_metadata(&component_metadata); - if !self.archetype_lookup.borrow().contains_key(&archetype_id) { - self.archetype_lookup.borrow_mut().insert( - archetype_id, - self.create_populated_archetype_lookup_entry(comp_metadata.as_ref()), - ); + ArchetypeRefIter { + storage: self, + iter: [archetype_id].into_iter().chain( + self.graph + .recurse_archetype_add_edges(archetype_id) + .into_iter() + .flatten() + .flatten(), + ), + visited: HashSet::new(), } - - self.iter_archetypes_by_lookup(archetype_id) } - pub fn get_entity_archetype(&self, entity_uid: Uid) -> Option<&Archetype> + pub fn get_archetype_by_id(&self, id: ArchetypeId) -> Option<&Archetype> { - let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?; - - let archetype_index = self.get_archetype_index_by_id(*archetype_id)?; - - self.archetypes.get(archetype_index) + Some(self.graph.get_node_by_id(id)?.archetype()) } - pub fn remove_entity(&mut self, entity_uid: Uid) + pub fn create_entity(&mut self, uid: Uid) -> Result<(), Error> { - let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { - return; - }; + debug_assert_eq!(uid.kind(), UidKind::Entity); - let Some(archetype_index) = self.get_archetype_index_by_id(*archetype_id) else { - return; - }; - - let Some(archetype) = self.archetypes.get_mut(archetype_index) else { - return; - }; - - archetype.take_entity(entity_uid); - - self.entity_archetype_lookup.remove(&entity_uid); - } - - #[tracing::instrument(skip_all)] - pub fn push_entity( - &mut self, - entity_uid: Uid, - mut components: Vec<Box<dyn Component>>, - ) -> Result<(ArchetypeId, Uid), Error> - { - if self.entity_archetype_lookup.contains_key(&entity_uid) { - return Err(Error::EntityAlreadyExists(entity_uid)); + if self.entity_archetype_lookup.contains_key(&uid) { + return Err(Error::EntityAlreadyExists(uid)); } - components.sort_by_key(|component| component.self_id()); - tracing::trace!( - "Pushing entity with components: ({})", - &components.iter().fold( - String::with_capacity(components.len() * 25), - |mut acc, component| { - acc.extend([", ", component.type_name()]); - acc - } - )[2..] - ); - - let archetype_id = ArchetypeId::from_components_metadata( - &components - .iter() - .map(|component| ComponentMetadata::get(&**component)) - .collect::<Vec<_>>(), - ); - - let comp_ids_set = create_non_opt_component_id_set( - components - .iter() - .map(|component| ComponentMetadata::get(&**component)), - ); - - let archetype_index = self.get_or_create_archetype(archetype_id, &components); + let empty_archetype_id = ArchetypeId::from_components_metadata(&[]); - self.populate_matching_archetype_lookup_entries(&comp_ids_set, archetype_index); + let archetype_node = self.graph.get_or_create_node(empty_archetype_id, &[]); - let archetype = self - .archetypes - .get_mut(archetype_index) - .expect("Archetype is gone"); + archetype_node + .archetype_mut() + .push_entity(ArchetypeEntity { uid, components: vec![] }); - archetype.push_entity(entity_uid, components); + self.entity_archetype_lookup.insert(uid, empty_archetype_id); - self.entity_archetype_lookup - .insert(entity_uid, archetype_id); - - Ok((archetype_id, entity_uid)) + Ok(()) } - pub fn add_components_to_entity( + pub fn remove_entity( &mut self, entity_uid: Uid, - components: Vec<Box<dyn Component>>, - ) -> Option<()> + ) -> Result<Vec<EntityComponent>, Error> { - let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?; - - let archetype_index = self.get_archetype_index_by_id(*archetype_id)?; - - let archetype = self.archetypes.get_mut(archetype_index)?; + let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { + return Err(Error::EntityDoesNotExist(entity_uid)); + }; - let contains_component_already = components - .iter() - .any(|component| archetype.component_ids.contains_key(&component.self_id())); - - if contains_component_already { - let component_cnt = components.len(); - - // TODO: return error - panic!( - "Entity with UID {:?} already contains one or more component(s) ({})", - entity_uid, - components - .iter() - .flat_map(|component| [component.type_name(), ", "]) - .enumerate() - .take_while(|(index, _)| { *index < (component_cnt * 2) - 1 }) - .map(|(_, component)| component) - .collect::<String>() - ); - } + let archetype_node = self + .graph + .get_node_by_id_mut(*archetype_id) + .expect("Archetype should exist"); - let entity = archetype.take_entity(entity_uid)?; + let entity = archetype_node + .archetype_mut() + .remove_entity(entity_uid) + .expect("Entity should exist in archetype"); self.entity_archetype_lookup.remove(&entity_uid); - self.push_entity( - entity_uid, - entity - .components - .into_iter() - .map(|component| component.component.into_inner()) - .chain(components) - .collect(), - ) - .expect("Not supposed to return Err since the entity is removed"); - - Some(()) + Ok(entity.components) } - pub fn remove_components_from_entity( - &mut self, - entity_uid: Uid, - component_ids: impl IntoIterator<Item = Uid>, - ) -> Option<()> + pub fn get_entity_archetype(&self, entity_uid: Uid) -> Option<&Archetype> { let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?; - let archetype_index = self.get_archetype_index_by_id(*archetype_id)?; - - let archetype = self.archetypes.get_mut(archetype_index)?; - - let entity = archetype.take_entity(entity_uid)?; - - let component_ids_set = component_ids.into_iter().collect::<HashSet<_>>(); - - self.entity_archetype_lookup.remove(&entity_uid); - - self.push_entity( - entity_uid, - entity - .components - .into_iter() - .map(|component| component.component.into_inner()) - .filter(|component| !component_ids_set.contains(&component.self_id())) - .collect(), - ) - .expect("Not supposed to return Err since the entity is removed"); - - Some(()) - } - - fn populate_matching_archetype_lookup_entries( - &mut self, - comp_ids_set: &HashSet<Uid>, - archetype_index: usize, - ) - { - let mut archetype_lookup = self.archetype_lookup.borrow_mut(); - - for (_, lookup_entry) in archetype_lookup.iter_mut() { - if &lookup_entry.component_ids == comp_ids_set { - continue; - } - - // There shouldn't be duplicate archetype indices in the lookup entry - if lookup_entry.archetype_indices.contains(&archetype_index) { - continue; - } - - if lookup_entry.component_ids.is_subset(comp_ids_set) { - lookup_entry.archetype_indices.push(archetype_index); - } - } + self.get_archetype_by_id(*archetype_id) } - fn get_or_create_archetype( + pub fn add_entity_component( &mut self, - archetype_id: ArchetypeId, - components: &[Box<dyn Component>], - ) -> usize - { - let mut archetype_lookup = self.archetype_lookup.borrow_mut(); - - if !archetype_lookup.contains_key(&archetype_id) { - self.archetypes.push(Archetype::new( - components.iter().map(|component| component.self_id()), - )); - } - - let lookup_entry = archetype_lookup.entry(archetype_id).or_insert_with(|| { - self.create_populated_archetype_lookup_entry( - components - .iter() - .map(|component| ComponentMetadata::get(&**component)), - ) - }); - - // SAFETY: Above, we push a archetype index if archetype_indices is empty so this - // cannot fail - unsafe { *lookup_entry.archetype_indices.first().unwrap_unchecked() } - } - - fn get_archetype_index_by_id(&self, archetype_id: ArchetypeId) -> Option<usize> + entity_uid: Uid, + component: Box<dyn Component>, + ) -> Result<(), Error> { - let archetype_lookup = self.archetype_lookup.borrow(); - - let archetype_lookup_entry = archetype_lookup.get(&archetype_id)?; - - let index = *archetype_lookup_entry - .archetype_indices - .first() - .expect("No archetype indices in archetype lookup entry"); - debug_assert!( - self.archetypes.get(index).is_some_and(|archetype| archetype - .component_ids_is(&archetype_lookup_entry.component_ids)), - "Archetype components is not exact match" + !component.self_is_optional(), + "Adding a optional component to a entity is not supported" ); - Some(index) - } - - fn iter_archetypes_by_lookup(&self, archetype_id: ArchetypeId) - -> ArchetypeRefIter<'_> - { - let archetype_lookup = self.archetype_lookup.borrow(); - - // The archetype indices have to be cloned to prevent a panic when query - // iterations are nested. The panic happens because the archetype_lookup RefCell - // is borrowed and it tries to mutably borrow it - let archetype_indices = archetype_lookup - .get(&archetype_id) - .unwrap() - .archetype_indices - .clone(); + let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { + return Err(Error::EntityDoesNotExist(entity_uid)); + }; - ArchetypeRefIter { - indices: archetype_indices.into_iter(), - archetypes: &self.archetypes, + let archetype_id = *archetype_id; + + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + if archetype_node + .archetype() + .has_component_with_id(component.self_id()) + { + return Err(Error::EntityAlreadyHasComponent { + entity: entity_uid, + component: component.self_id(), + }); } - } - - fn create_populated_archetype_lookup_entry<CompMetadataIter>( - &self, - comp_metadata_iter: CompMetadataIter, - ) -> ArchetypeLookupEntry - where - CompMetadataIter: IntoIterator<Item: Borrow<ComponentMetadata>>, - { - let comp_ids_set = create_non_opt_component_id_set(comp_metadata_iter); - - let mut exact_matching_archetype_index = None; - - let matching_archetype_indices = self - .archetypes - .iter() - .enumerate() - .filter_map(|(index, archetype)| { - if archetype.component_ids_is(&comp_ids_set) { - exact_matching_archetype_index = Some(index); - return None; + let add_edge_archetype_id = archetype_node + .get_or_insert_edges(component.self_id(), ArchetypeEdges::default) + .add + .unwrap_or_else(|| { + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + let (add_edge_id, add_edge_comp_ids) = + archetype_node.make_add_edge(component.self_id()); + + archetype_node + .get_edges_mut(component.self_id()) + .expect("Edges for component in archetype should exist") + .add = Some(add_edge_id); + + if !self.graph.contains_archetype(add_edge_id) { + self.graph.create_node(add_edge_id, &add_edge_comp_ids); } - if archetype.component_ids_is_superset(&comp_ids_set) { - return Some(index); - } + add_edge_id + }); - None - }) - .collect::<Vec<_>>(); + { + let add_edge_archetype_node = self + .graph + .get_node_by_id_mut(add_edge_archetype_id) + .expect("Add edge archetype should exist"); - ArchetypeLookupEntry { - component_ids: comp_ids_set, - archetype_indices: exact_matching_archetype_index - .into_iter() - .chain(matching_archetype_indices) - .collect(), - } - } -} + let add_edge_archetype_edges = add_edge_archetype_node + .get_or_insert_edges(component.self_id(), ArchetypeEdges::default); -/// Component storage error -#[derive(Debug, Clone, thiserror::Error)] -pub enum Error -{ - #[error("Entity already exists")] - EntityAlreadyExists(Uid), -} - -impl TypeName for Storage -{ - fn type_name(&self) -> &'static str - { - type_name::<Self>() - } -} - -#[derive(Debug)] -struct ArchetypeLookupEntry -{ - component_ids: HashSet<Uid>, - archetype_indices: Vec<usize>, -} - -#[derive(Debug)] -pub struct Archetype -{ - component_ids: HashMap<Uid, usize>, - entity_lookup: HashMap<Uid, usize>, - entity_uid_lookup: Vec<Uid>, - entities: Vec<ArchetypeEntity>, -} - -impl Archetype -{ - fn new(component_ids: impl IntoIterator<Item = Uid>) -> Self - { - Self { - component_ids: component_ids - .into_iter() - .enumerate() - .map(|(index, component_id)| (component_id, index)) - .collect(), - entity_lookup: HashMap::new(), - entity_uid_lookup: Vec::new(), - entities: Vec::new(), + add_edge_archetype_edges.remove = Some(archetype_id); } - } - pub fn component_ids_is_superset(&self, other_component_ids: &HashSet<Uid>) -> bool - { - if other_component_ids.len() <= self.component_ids.len() { - other_component_ids - .iter() - .all(|v| self.component_ids.contains_key(v)) - } else { - false - } - } + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); - pub fn component_ids_is(&self, other_component_ids: &HashSet<Uid>) -> bool - { - if other_component_ids.len() == self.component_ids.len() { - other_component_ids - .iter() - .all(|v| self.component_ids.contains_key(v)) - } else { - false - } - } + let mut entity = archetype_node + .archetype_mut() + .remove_entity(entity_uid) + .expect("Entity should exist in archetype"); - pub fn get_entity(&self, entity_uid: Uid) -> Option<&ArchetypeEntity> - { - let entity_index = *self.entity_lookup.get(&entity_uid)?; + let add_edge_archetype = self + .graph + .get_node_by_id_mut(add_edge_archetype_id) + .expect("Add edge archetype should exist") + .archetype_mut(); - self.entities.get(entity_index) - } + let component_index = add_edge_archetype + .get_index_for_component(component.self_id()) + .expect("Archetype should have index for component"); - pub fn entities(&self) -> EntityIter<'_> - { - EntityIter { iter: self.entities.iter() } - } + entity.components.insert(component_index, component.into()); - pub fn entity_cnt(&self) -> usize - { - self.entities.len() - } + add_edge_archetype.push_entity(entity); - pub fn get_index_for_component(&self, component_id: Uid) -> Option<usize> - { - self.component_ids.get(&component_id).copied() + self.entity_archetype_lookup + .insert(entity_uid, add_edge_archetype_id); + + Ok(()) } - fn push_entity( + pub fn remove_entity_component( &mut self, entity_uid: Uid, - components: impl IntoIterator<Item = Box<dyn Component>>, - ) + component_id: Uid, + ) -> Result<(), Error> { - self.entities.push(ArchetypeEntity { - uid: entity_uid, - components: components.into_iter().map(Into::into).collect(), - }); + let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { + return Err(Error::EntityDoesNotExist(entity_uid)); + }; - let index = self.entities.len() - 1; + let archetype_id = *archetype_id; + + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + if !archetype_node + .archetype() + .has_component_with_id(component_id) + { + return Err(Error::EntityDoesNotHaveComponent { + entity: entity_uid, + component: component_id, + }); + } - self.entity_lookup.insert(entity_uid, index); + let remove_edge_id = archetype_node + .get_or_insert_edges(component_id, ArchetypeEdges::default) + .remove + .unwrap_or_else(|| { + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + let (remove_edge_id, remove_edge_comp_ids) = + archetype_node.make_remove_edge(component_id); + + archetype_node + .get_edges_mut(component_id) + .expect("Edges for component in archetype should exist") + .remove = Some(remove_edge_id); + + if !self.graph.contains_archetype(remove_edge_id) { + self.graph + .create_node(remove_edge_id, &remove_edge_comp_ids); + } - self.entity_uid_lookup.push(entity_uid); - } + remove_edge_id + }); - pub fn take_entity(&mut self, entity_uid: Uid) -> Option<ArchetypeEntity> - { - let entity_index = self.entity_lookup.remove(&entity_uid)?; + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); - let last_entity_uid = *self - .entity_uid_lookup - .get(self.entities.len() - 1) - .expect("Entity UID lookup contains too few entity UIDS"); + let mut entity = archetype_node + .archetype_mut() + .remove_entity(entity_uid) + .expect("Entity should exist in archetype"); - // By using swap_remove, no memory reallocation occurs and only one index in the - // entity lookup needs to be updated - let removed_entity = self.entities.swap_remove(entity_index); + let (comp_to_remove_index, _) = entity + .components + .iter() + .enumerate() + .find(|(_, comp)| comp.id == component_id) + .expect("Entity should contain component"); - self.entity_lookup.insert(last_entity_uid, entity_index); + entity.components.remove(comp_to_remove_index); - self.entity_uid_lookup.swap_remove(entity_index); + self.graph + .get_node_by_id_mut(remove_edge_id) + .expect("Remove edge archetype should exist") + .archetype_mut() + .push_entity(entity); - Some(removed_entity) - } -} + self.entity_archetype_lookup + .insert(entity_uid, remove_edge_id); -#[derive(Debug)] -pub struct ArchetypeEntity -{ - uid: Uid, - components: Vec<EntityComponent>, + Ok(()) + } } -impl ArchetypeEntity +impl TypeName for Storage { - pub fn uid(&self) -> Uid - { - self.uid - } - - pub fn components(&self) -> &[EntityComponent] - { - &self.components - } - - pub fn get_component(&self, index: usize) -> Option<&EntityComponent> + fn type_name(&self) -> &'static str { - self.components.get(index) + type_name::<Self>() } } #[derive(Debug)] -pub struct ArchetypeRefIter<'component_storage> +pub struct ArchetypeRefIter<'storage> { - indices: OwnedVecIter<usize>, - archetypes: &'component_storage [Archetype], + storage: &'storage Storage, + iter: Chain< + std::array::IntoIter<ArchetypeId, 1>, + Flatten<Flatten<std::option::IntoIter<ArchetypeAddEdgeRecursiveIter<'storage>>>>, + >, + visited: HashSet<ArchetypeId>, } impl<'component_storage> Iterator for ArchetypeRefIter<'component_storage> @@ -507,150 +315,67 @@ impl<'component_storage> Iterator for ArchetypeRefIter<'component_storage> fn next(&mut self) -> Option<Self::Item> { - let archetype_index = self.indices.next()?; + let archetype_id = self + .iter + .find(|archetype_id| !self.visited.contains(archetype_id))?; + + let archetype = self.storage.get_archetype_by_id(archetype_id)?; - Some( - self.archetypes - .get(archetype_index) - .expect("Archetype index in archetype lookup entry was not found"), - ) + self.visited.insert(archetype_id); + + Some(archetype) } } -#[derive(Debug)] -pub struct EntityIter<'archetype> +#[derive(Debug, thiserror::Error)] +pub enum Error { - iter: SliceIter<'archetype, ArchetypeEntity>, -} + #[error("Entity with ID {0:?} already exists")] + EntityAlreadyExists(Uid), -impl<'archetype> Iterator for EntityIter<'archetype> -{ - type Item = &'archetype ArchetypeEntity; + #[error("Entity with ID {0:?} does not exist")] + EntityDoesNotExist(Uid), - fn next(&mut self) -> Option<Self::Item> + #[error("Entity with ID {entity:?} already has component with ID {component:?}")] + EntityAlreadyHasComponent { - self.iter.next() - } -} + entity: Uid, component: Uid + }, -fn create_non_opt_component_id_set<Item>( - component_metadata_iter: impl IntoIterator<Item = Item>, -) -> HashSet<Uid> -where - Item: Borrow<ComponentMetadata>, -{ - component_metadata_iter - .into_iter() - .filter_map(|item| { - let component_metadata = item.borrow(); - - if component_metadata.is_optional { - return None; - } - - Some(component_metadata.id) - }) - .collect::<HashSet<_>>() + #[error("Entity with ID {entity:?} does not have component with ID {component:?}")] + EntityDoesNotHaveComponent + { + entity: Uid, component: Uid + }, } #[cfg(test)] mod tests { - - use ecs_macros::Component; - - use super::Storage; - use crate::archetype::Id as ArchetypeId; - use crate::component::{Component, Metadata as ComponentMetadata}; + use crate::component::storage::archetype::Id as ArchetypeId; + use crate::component::storage::Storage; use crate::uid::{Kind as UidKind, Uid}; - #[derive(Debug, Component)] - struct HealthPotion - { - _hp_restoration: u32, - } - - #[derive(Debug, Component)] - struct Hookshot - { - _range: u32, - } - - #[derive(Debug, Component)] - struct DekuNut - { - _throwing_damage: u32, - } - - #[derive(Debug, Component)] - struct Bow - { - _damage: u32, - } - - #[derive(Debug, Component)] - struct IronBoots; - #[test] - fn push_entity_works() + fn create_entity_works() { - let mut component_storage = Storage::default(); - - component_storage - .push_entity( - Uid::new_unique(UidKind::Entity), - vec![ - Box::new(HealthPotion { _hp_restoration: 12 }), - Box::new(Hookshot { _range: 50 }), - ], - ) - .expect("Expected Ok"); - - assert_eq!(component_storage.archetypes.len(), 1); - - let archetype = component_storage - .archetypes - .first() - .expect("Expected a archetype in archetypes Vec"); - - assert_eq!(archetype.component_ids.len(), 2); - - // One entity - assert_eq!(archetype.entities.len(), 1); + let mut new_storage = Storage::default(); - let entity_components = archetype - .entities - .first() - .expect("Expected a entity in archetype"); + let uid = Uid::new_unique(UidKind::Entity); - assert_eq!(entity_components.components.len(), 2); + new_storage.create_entity(uid).expect("Expected Ok"); - assert_eq!(component_storage.archetype_lookup.borrow().len(), 1); + let archetype_node = new_storage + .graph + .get_node_by_id(ArchetypeId::from_components_metadata(&[])) + .expect("Archetype for entities with no component doesn't exist"); - let mut components_metadata = [ - ComponentMetadata { - id: HealthPotion::id(), - is_optional: false, - }, - ComponentMetadata { - id: Hookshot::id(), - is_optional: false, - }, - ]; + assert_eq!(archetype_node.archetype().component_cnt(), 0); + assert_eq!(archetype_node.archetype().entity_cnt(), 1); - components_metadata.sort_by_key(|comp_metadata| comp_metadata.id); - - let archetype_lookup = component_storage.archetype_lookup.borrow(); - - let lookup_entry = archetype_lookup - .get(&ArchetypeId::from_components_metadata(&components_metadata)) - .expect("Expected entry in archetype lookup map"); - - let first_archetype_index = lookup_entry - .archetype_indices - .first() - .expect("Expected archetype lookup to contain a archetype reference"); - - assert_eq!(*first_archetype_index, 0); + assert_eq!( + new_storage.entity_archetype_lookup.get(&uid).copied(), + Some(ArchetypeId::from_components_metadata(&[])) + ); } } diff --git a/ecs/src/component/storage/archetype.rs b/ecs/src/component/storage/archetype.rs new file mode 100644 index 0000000..ee3d7f8 --- /dev/null +++ b/ecs/src/component/storage/archetype.rs @@ -0,0 +1,216 @@ +use std::borrow::Borrow; +use std::hash::{DefaultHasher, Hash, Hasher}; +use std::slice::Iter as SliceIter; + +use hashbrown::HashMap; + +use crate::component::Metadata as ComponentMetadata; +use crate::uid::{Kind as UidKind, Uid}; +use crate::util::HashMapExt; +use crate::EntityComponent; + +#[derive(Debug)] +pub struct Archetype +{ + id: Id, + entities: Vec<ArchetypeEntity>, + entity_index_lookup: HashMap<Uid, usize>, + component_index_lookup: HashMap<Uid, usize>, +} + +impl Archetype +{ + pub fn new(id: Id, component_ids: impl IntoIterator<Item: Borrow<Uid>>) -> Self + { + Self { + id, + entities: Vec::new(), + entity_index_lookup: HashMap::new(), + component_index_lookup: component_ids + .into_iter() + .enumerate() + .map(|(index, id)| (*id.borrow(), index)) + .collect(), + } + } + + pub fn id(&self) -> Id + { + self.id + } + + pub fn is_superset(&self, other: &Self) -> bool + { + self.component_index_lookup + .keys_is_superset(&other.component_index_lookup) + } + + pub fn get_entity_by_id(&self, entity_uid: Uid) -> Option<&ArchetypeEntity> + { + let index = *self.entity_index_lookup.get(&entity_uid)?; + + Some(self.entities.get(index).unwrap_or_else(|| { + panic!( + "In invalid state! Index of entity with ID {entity_uid:?} is out of bounds" + ); + })) + } + + pub fn push_entity(&mut self, entity: ArchetypeEntity) + { + self.entity_index_lookup + .insert(entity.uid, self.entities.len()); + + self.entities.push(entity); + } + + pub fn remove_entity(&mut self, entity_uid: Uid) -> Option<ArchetypeEntity> + { + //debug_assert_eq!(entity_uid.kind(), UidKind::Entity); + + let entity_index = self.entity_index_lookup.remove(&entity_uid)?; + + if self.entities.len() == 1 { + return Some(self.entities.remove(entity_index)); + } + + let last_entity_uid = self + .entities + .last() + .expect(concat!( + "Invalid state. No entities in archetype but entry was ", + "removed successfully from entity index lookup" + )) + .uid; + + // By using swap_remove, no memory reallocation occurs and only one index in the + // entity lookup needs to be updated + let removed_entity = self.entities.swap_remove(entity_index); + + self.entity_index_lookup + .insert(last_entity_uid, entity_index); + + Some(removed_entity) + } + + pub fn entities(&self) -> EntityIter<'_> + { + EntityIter { iter: self.entities.iter() } + } + + pub fn entity_cnt(&self) -> usize + { + self.entities.len() + } + + pub fn component_cnt(&self) -> usize + { + self.component_index_lookup.len() + } + + pub fn get_index_for_component(&self, component_id: Uid) -> Option<usize> + { + self.component_index_lookup.get(&component_id).copied() + } + + pub fn component_ids_unsorted(&self) -> impl Iterator<Item = Uid> + '_ + { + self.component_index_lookup.keys().copied() + } + + pub fn has_component_with_id(&self, component_id: Uid) -> bool + { + debug_assert_eq!(component_id.kind(), UidKind::Component); + + self.component_index_lookup.contains_key(&component_id) + } +} + +#[derive(Debug)] +pub struct EntityIter<'archetype> +{ + iter: SliceIter<'archetype, ArchetypeEntity>, +} + +impl<'archetype> Iterator for EntityIter<'archetype> +{ + type Item = &'archetype ArchetypeEntity; + + fn next(&mut self) -> Option<Self::Item> + { + self.iter.next() + } +} + +#[derive(Debug)] +pub struct ArchetypeEntity +{ + pub uid: Uid, + pub components: Vec<EntityComponent>, +} + +/// Archetype ID. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Id +{ + hash: u64, +} + +impl Id +{ + pub fn new(component_ids: &impl AsRef<[Uid]>) -> Self + { + if component_ids.as_ref().len() == 0 { + return Self { hash: 0 }; + } + + debug_assert!( + component_ids.as_ref().is_sorted(), + "Cannot create archetype ID from unsorted component IDs" + ); + + let mut hasher = DefaultHasher::new(); + + for component_id in component_ids.as_ref() { + component_id.hash(&mut hasher); + } + + Self { hash: hasher.finish() } + } + + pub fn from_components_metadata( + components_metadata: &impl AsRef<[ComponentMetadata]>, + ) -> Self + { + if components_metadata.as_ref().len() == 0 { + return Self { hash: 0 }; + } + + debug_assert!( + components_metadata + .as_ref() + .is_sorted_by_key(|comp_metadata| comp_metadata.id), + "Cannot create archetype ID from a unsorted component metadata list" + ); + + let component_ids = + components_metadata + .as_ref() + .iter() + .filter_map(|component_metadata| { + if component_metadata.is_optional { + return None; + } + + Some(component_metadata.id) + }); + + let mut hasher = DefaultHasher::new(); + + for component_id in component_ids { + component_id.hash(&mut hasher); + } + + Self { hash: hasher.finish() } + } +} diff --git a/ecs/src/component/storage/graph.rs b/ecs/src/component/storage/graph.rs new file mode 100644 index 0000000..fe97933 --- /dev/null +++ b/ecs/src/component/storage/graph.rs @@ -0,0 +1,269 @@ +use hashbrown::hash_map::Values as HashMapValueIter; +use hashbrown::HashMap; + +use crate::component::storage::archetype::{Archetype, Id as ArchetypeId}; +use crate::uid::{Kind as UidKind, Uid}; + +#[derive(Debug, Default)] +pub struct Graph +{ + nodes: Vec<ArchetypeNode>, + archetype_index_lookup: HashMap<ArchetypeId, usize>, +} + +impl Graph +{ + pub fn create_node(&mut self, id: ArchetypeId, component_ids: &impl AsRef<[Uid]>) + { + debug_assert!(!self.contains_archetype(id)); + + let _ = self.get_or_create_node(id, component_ids); + } + + pub fn get_or_create_node( + &mut self, + id: ArchetypeId, + component_ids: &impl AsRef<[Uid]>, + ) -> &mut ArchetypeNode + { + let index = *self.archetype_index_lookup.entry(id).or_insert_with(|| { + self.nodes.push(ArchetypeNode { + archetype: Archetype::new(id, component_ids.as_ref()), + edges: HashMap::new(), + }); + + self.nodes.len() - 1 + }); + + self.create_missing_edges(id); + + self.nodes + .get_mut(index) + .expect("Archetype index from lookup is out of bounds") + } + + pub fn contains_archetype(&self, id: ArchetypeId) -> bool + { + self.archetype_index_lookup.contains_key(&id) + } + + pub fn get_node_by_id(&self, id: ArchetypeId) -> Option<&ArchetypeNode> + { + let index = self.archetype_index_lookup.get(&id)?; + + Some(self.nodes.get(*index).unwrap_or_else(|| { + panic!("In invalid state! Index of archetype with ID {id:?} is out of bounds") + })) + } + + pub fn get_node_by_id_mut(&mut self, id: ArchetypeId) -> Option<&mut ArchetypeNode> + { + let index = self.archetype_index_lookup.get(&id)?; + + Some(self.nodes.get_mut(*index).unwrap_or_else(|| { + panic!("In invalid state! Index of archetype with ID {id:?} is out of bounds") + })) + } + + pub fn recurse_archetype_add_edges( + &self, + archetype_id: ArchetypeId, + ) -> Option<ArchetypeAddEdgeRecursiveIter> + { + Some(ArchetypeAddEdgeRecursiveIter { + graph: self, + stack: vec![self.get_node_by_id(archetype_id)?.edges.values()], + }) + } + + fn create_missing_edges(&mut self, archetype_id: ArchetypeId) + { + let archetype_node = self + .get_node_by_id(archetype_id) + .expect("Archetype should exist"); + + let mut comp_ids = archetype_node + .archetype() + .component_ids_unsorted() + .collect::<Vec<_>>(); + + comp_ids.sort(); + + for component_id_index in 0..archetype_node.archetype().component_cnt() { + let mut comp_ids_without_comp = comp_ids.clone(); + + let removed_comp_id = comp_ids_without_comp.remove(component_id_index); + + let archetype_without_comp_id = ArchetypeId::new(&comp_ids_without_comp); + + let Some(archetype_node_without_comp) = + self.get_node_by_id_mut(archetype_without_comp_id) + else { + continue; + }; + + archetype_node_without_comp + .get_or_insert_edges(removed_comp_id, ArchetypeEdges::default) + .add = Some(archetype_id); + + let archetype_node = self + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + archetype_node + .get_or_insert_edges(removed_comp_id, ArchetypeEdges::default) + .remove = Some(archetype_without_comp_id); + } + + let archetype_node_index = *self + .archetype_index_lookup + .get(&archetype_id) + .expect("Archetype should exist"); + + let (nodes_before, nodes_rest) = self.nodes.split_at_mut(archetype_node_index); + + let ([archetype_node], nodes_after) = nodes_rest.split_at_mut(1) else { + unreachable!(); + }; + + let expected_component_cnt = archetype_node.archetype().component_cnt() + 1; + + for other_archetype_node in nodes_before.iter_mut().chain(nodes_after.iter_mut()) + { + if other_archetype_node.archetype().component_cnt() != expected_component_cnt + || !other_archetype_node + .archetype() + .is_superset(&archetype_node.archetype()) + { + continue; + } + + let extra_comp_id = other_archetype_node + .archetype() + .component_ids_unsorted() + .find(|comp_id| { + !archetype_node.archetype().has_component_with_id(*comp_id) + }) + .expect("Archetype should contain one extra component ID"); + + other_archetype_node + .get_or_insert_edges(extra_comp_id, ArchetypeEdges::default) + .remove = Some(archetype_id); + + archetype_node + .get_or_insert_edges(extra_comp_id, ArchetypeEdges::default) + .add = Some(other_archetype_node.archetype().id()); + } + } +} + +#[derive(Debug)] +pub struct ArchetypeNode +{ + archetype: Archetype, + edges: HashMap<Uid, ArchetypeEdges>, +} + +impl ArchetypeNode +{ + pub fn archetype(&self) -> &Archetype + { + &self.archetype + } + + pub fn archetype_mut(&mut self) -> &mut Archetype + { + &mut self.archetype + } + + pub fn get_or_insert_edges( + &mut self, + component_id: Uid, + insert_fn: impl FnOnce() -> ArchetypeEdges, + ) -> &mut ArchetypeEdges + { + debug_assert_eq!(component_id.kind(), UidKind::Component); + + self.edges.entry(component_id).or_insert_with(insert_fn) + } + + pub fn get_edges_mut(&mut self, component_id: Uid) -> Option<&mut ArchetypeEdges> + { + debug_assert_eq!(component_id.kind(), UidKind::Component); + + self.edges.get_mut(&component_id) + } + + pub fn make_add_edge(&self, component_id: Uid) -> (ArchetypeId, Vec<Uid>) + { + let mut edge_comp_ids = self + .archetype() + .component_ids_unsorted() + .chain([component_id]) + .collect::<Vec<_>>(); + + edge_comp_ids.sort(); + + let add_edge_id = ArchetypeId::new(&edge_comp_ids); + + (add_edge_id, edge_comp_ids) + } + + pub fn make_remove_edge(&self, component_id: Uid) -> (ArchetypeId, Vec<Uid>) + { + let mut edge_comp_ids = self + .archetype() + .component_ids_unsorted() + .filter(|id| *id != component_id) + .collect::<Vec<_>>(); + + edge_comp_ids.sort(); + + let remove_edge_id = ArchetypeId::new(&edge_comp_ids); + + (remove_edge_id, edge_comp_ids) + } +} + +#[derive(Debug, Default)] +pub struct ArchetypeEdges +{ + pub add: Option<ArchetypeId>, + pub remove: Option<ArchetypeId>, +} + +#[derive(Debug)] +pub struct ArchetypeAddEdgeRecursiveIter<'graph> +{ + graph: &'graph Graph, + stack: Vec<HashMapValueIter<'graph, Uid, ArchetypeEdges>>, +} + +impl<'graph> Iterator for ArchetypeAddEdgeRecursiveIter<'graph> +{ + type Item = Option<ArchetypeId>; + + fn next(&mut self) -> Option<Self::Item> + { + let iter = self.stack.last_mut()?; + + let Some(edges) = iter.next() else { + self.stack.pop(); + + return Some(None); + }; + + let Some(add_edge) = edges.add else { + return Some(None); + }; + + let add_edge_archetype = self + .graph + .get_node_by_id(add_edge) + .expect("Add edge archetype not found"); + + self.stack.push(add_edge_archetype.edges.values()); + + Some(Some(add_edge)) + } +} diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs index 72b5cf9..dc31daf 100644 --- a/ecs/src/lib.rs +++ b/ecs/src/lib.rs @@ -52,8 +52,6 @@ pub mod util; #[doc(hidden)] pub mod private; -mod archetype; - pub use ecs_macros::{Component, Sole}; pub use crate::query::Query; @@ -109,18 +107,20 @@ impl World { debug_assert_eq!(entity_uid.kind(), UidKind::Entity); - #[allow(unused_variables)] - if let Err(err) = self - .data - .component_storage - .write_nonblock() - .expect("Failed to acquire read-write component storage lock") - .push_entity(entity_uid, components.into_array().into()) { - tracing::error!("Failed to create entity: {err}"); + let mut component_storage_lock = self.lock_component_storage_rw(); - return; - }; + if let Err(err) = component_storage_lock.create_entity(entity_uid) { + tracing::warn!("Failed to create entity: {err}"); + return; + }; + + Self::add_entity_components( + entity_uid, + components.into_array(), + &mut component_storage_lock, + ); + } for added_event_id in Comps::added_event_ids() { self.emit_event_by_id(added_event_id); @@ -316,19 +316,25 @@ impl World for action in active_action_queue.drain(..) { match action { Action::Spawn(components, component_added_event_ids) => { - let mut component_storage_lock = self.lock_component_storage_rw(); - - #[allow(unused_variables)] - if let Err(err) = component_storage_lock - .push_entity(Uid::new_unique(UidKind::Entity), components) { - tracing::error!("Failed to create entity: {err}"); - - continue; + let mut component_storage_lock = self.lock_component_storage_rw(); + + let new_entity_uid = Uid::new_unique(UidKind::Entity); + + if let Err(err) = + component_storage_lock.create_entity(new_entity_uid) + { + tracing::warn!("Failed to create entity: {err}"); + continue; + }; + + Self::add_entity_components( + new_entity_uid, + components, + &mut component_storage_lock, + ); } - drop(component_storage_lock); - if !has_swapped_active_queue { self.swap_event_queue(&mut has_swapped_active_queue); } @@ -345,17 +351,23 @@ impl World components, component_added_event_ids, ) => { - let mut component_storage_lock = self.lock_component_storage_rw(); - - component_storage_lock - .add_components_to_entity(entity_uid, components); + { + let mut component_storage_lock = self.lock_component_storage_rw(); - drop(component_storage_lock); + Self::add_entity_components( + entity_uid, + components, + &mut component_storage_lock, + ); + } if !has_swapped_active_queue { self.swap_event_queue(&mut has_swapped_active_queue); } + // TODO: Fix that events are emitted for components that haven't been + // added because a error occurred (for example, the entity already has + // the component) for comp_added_event_id in component_added_event_ids.ids { self.emit_event_by_id(comp_added_event_id); } @@ -365,21 +377,25 @@ impl World components_metadata, component_removed_event_ids, ) => { - let mut component_storage_lock = self.lock_component_storage_rw(); - - component_storage_lock.remove_components_from_entity( - entity_uid, - components_metadata - .iter() - .map(|component_metadata| component_metadata.id), - ); - - drop(component_storage_lock); + { + let mut component_storage_lock = self.lock_component_storage_rw(); + + Self::remove_entity_components( + entity_uid, + components_metadata + .iter() + .map(|comp_metadata| comp_metadata.id), + &mut component_storage_lock, + ); + } if !has_swapped_active_queue { self.swap_event_queue(&mut has_swapped_active_queue); } + // TODO: Fix that events are emitted for components that haven't been + // removed because a error occurred (for example, the entity does not + // have the component) for comp_removed_event_id in component_removed_event_ids.ids { self.emit_event_by_id(comp_removed_event_id); } @@ -391,23 +407,20 @@ impl World } } + #[tracing::instrument(skip_all)] fn despawn_entity(&self, entity_uid: Uid, has_swapped_active_queue: &mut bool) { let mut component_storage_lock = self.lock_component_storage_rw(); - let Some(archetype) = component_storage_lock.get_entity_archetype(entity_uid) - else { - tracing::error!("No archetype for entity {entity_uid:?} was found"); - - return; + let components = match component_storage_lock.remove_entity(entity_uid) { + Ok(components) => components, + Err(err) => { + tracing::error!("Failed to despawn entity: {err}"); + return; + } }; - let entity = archetype - .get_entity(entity_uid) - .expect("Entity archetype was found but the entity is not in the archetype"); - - let component_removed_event_uids = entity - .components() + let component_removed_event_uids = components .iter() .map(|component| { component @@ -423,8 +436,6 @@ impl World }) .collect::<Vec<_>>(); - component_storage_lock.remove_entity(entity_uid); - drop(component_storage_lock); if !*has_swapped_active_queue { @@ -436,6 +447,36 @@ impl World } } + fn add_entity_components( + entity_uid: Uid, + components: impl IntoIterator<Item = Box<dyn Component>>, + component_storage: &mut ComponentStorage, + ) + { + for component in components.into_iter() { + if let Err(err) = + component_storage.add_entity_component(entity_uid, component) + { + tracing::error!("Failed to add component to entity: {err}"); + } + } + } + + fn remove_entity_components( + entity_uid: Uid, + component_ids: impl IntoIterator<Item = Uid>, + component_storage: &mut ComponentStorage, + ) + { + for component_id in component_ids.into_iter() { + if let Err(err) = + component_storage.remove_entity_component(entity_uid, component_id) + { + tracing::error!("Failed to remove component to entity: {err}"); + } + } + } + fn emit_event_by_id(&self, event_id: Uid) { let query = self.flexible_query([ diff --git a/ecs/src/query/flexible.rs b/ecs/src/query/flexible.rs index 5c23e68..129ee66 100644 --- a/ecs/src/query/flexible.rs +++ b/ecs/src/query/flexible.rs @@ -1,13 +1,8 @@ //! Low-level querying. use std::iter::{repeat_n, Filter, Flatten, Map, RepeatN, Zip}; -use crate::component::storage::{ - Archetype, - ArchetypeEntity, - ArchetypeRefIter, - EntityIter, - Storage as ComponentStorage, -}; +use crate::component::storage::archetype::{Archetype, ArchetypeEntity, EntityIter}; +use crate::component::storage::{ArchetypeRefIter, Storage as ComponentStorage}; use crate::component::{ Metadata as ComponentMetadata, RefSequence as ComponentRefSequence, @@ -48,7 +43,7 @@ where }) as ComponentIterMapFn, ) .flatten() - .filter(|(_, entity)| OptionsT::entity_filter(entity.components())), + .filter(|(_, entity)| OptionsT::entity_filter(&entity.components)), } } @@ -117,13 +112,13 @@ impl<'query> EntityHandle<'query> #[inline] pub fn uid(&self) -> Uid { - self.entity.uid() + self.entity.uid } #[inline] pub fn components(&self) -> &'query [EntityComponent] { - self.entity.components() + &self.entity.components } #[inline] diff --git a/ecs/src/relationship.rs b/ecs/src/relationship.rs index 143b589..54dc0cd 100644 --- a/ecs/src/relationship.rs +++ b/ecs/src/relationship.rs @@ -174,14 +174,15 @@ where let archetype = self.component_storage_lock.get_entity_archetype(*target)?; let entity = archetype - .get_entity(*target) + .get_entity_by_id(*target) .expect("Target entity is gone from archetype"); let component_index = archetype.get_index_for_component(ComponentT::id())?; let component = ComponentRefMut::new( entity - .get_component(component_index)? + .components + .get(component_index)? .component .write_nonblock() .unwrap_or_else(|_| { @@ -430,14 +431,15 @@ where let archetype = self.component_storage_lock.get_entity_archetype(*target)?; let entity = archetype - .get_entity(*target) + .get_entity_by_id(*target) .expect("Target entity is gone from archetype"); let component_index = archetype.get_index_for_component(ComponentT::id())?; let component = ComponentRef::new( entity - .get_component(component_index)? + .components + .get(component_index)? .component .read_nonblock() .unwrap_or_else(|_| { diff --git a/ecs/src/util.rs b/ecs/src/util.rs index 0273b18..71021cd 100644 --- a/ecs/src/util.rs +++ b/ecs/src/util.rs @@ -1,5 +1,38 @@ +use std::hash::Hash; use std::ops::BitAnd; +use hashbrown::HashMap; + +pub trait HashMapExt<Key, Value> +{ + /// Returns true if the keys are a subset of another [`HashMap`]'s keys, i.e., `other` + /// contains at least all the keys in `self`. + fn keys_is_subset(&self, other: &Self) -> bool; + + /// Returns true if the keys are a superset of another [`HashMap`]'s keys, i.e., + /// `self` contains at least all the keys in `other`. + fn keys_is_superset(&self, other: &Self) -> bool; +} + +impl<Key, Value> HashMapExt<Key, Value> for HashMap<Key, Value> +where + Key: Eq + Hash, +{ + fn keys_is_subset(&self, other: &Self) -> bool + { + if self.len() <= other.len() { + self.keys().all(|key| other.contains_key(key)) + } else { + false + } + } + + fn keys_is_superset(&self, other: &Self) -> bool + { + other.keys_is_subset(self) + } +} + pub trait Array<Item>: AsRef<[Item]> + AsMut<[Item]> |