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, entity_index_lookup: HashMap, component_index_lookup: HashMap, } impl Archetype { pub fn new(id: Id, component_ids: impl IntoIterator>) -> 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 { //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 { self.component_index_lookup.get(&component_id).copied() } pub fn component_ids_unsorted(&self) -> impl Iterator + '_ { 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.iter.next() } } #[derive(Debug)] pub struct ArchetypeEntity { pub uid: Uid, pub components: Vec, } /// 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() } } }