diff options
| author | HampusM <hampus@hampusmat.com> | 2026-05-21 17:55:20 +0200 |
|---|---|---|
| committer | HampusM <hampus@hampusmat.com> | 2026-05-21 17:55:20 +0200 |
| commit | 8022e8998290b067b8aa0cb9cba8ba410826bdab (patch) | |
| tree | 7171e79ce530e03079046ee8fd12167160c45480 /engine-ecs/src/lib.rs | |
| parent | 412cee02c252f91bcf0b70a3f5cc5fca6d2b4c62 (diff) | |
Diffstat (limited to 'engine-ecs/src/lib.rs')
| -rw-r--r-- | engine-ecs/src/lib.rs | 773 |
1 files changed, 773 insertions, 0 deletions
diff --git a/engine-ecs/src/lib.rs b/engine-ecs/src/lib.rs new file mode 100644 index 0000000..6450587 --- /dev/null +++ b/engine-ecs/src/lib.rs @@ -0,0 +1,773 @@ +#![deny(clippy::all, clippy::pedantic)] + +use std::any::{Any, TypeId, type_name}; +use std::fmt::Debug; +use std::hint::cold_path; +use std::mem::ManuallyDrop; +use std::rc::Rc; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; + +use hashbrown::HashMap; + +use crate::actions::Action; +use crate::component::storage::archetype::EntityComponent as ArchetypeEntityComponent; +use crate::component::storage::{EntityAlreadyExistsError, Storage as ComponentStorage}; +use crate::component::{ + Component, + IntoParts as IntoComponentParts, + Parts as ComponentParts, + Sequence as ComponentSequence, +}; +use crate::entity::{Declaration as EntityDeclaration, Handle as EntityHandle}; +use crate::error::{ + ErrorHandler, + Metadata as ErrorMetadata, + SourceKind as ErrorSourceKind, + err_handler_panic, +}; +use crate::event::component::Added; +use crate::event::{Emitted as EmittedEvent, NewEvents, Submitter as EventSubmitter}; +use crate::extension::{Collector as ExtensionCollector, Extension}; +use crate::lock::Lock; +use crate::pair::{ChildOf, Pair, Wildcard}; +use crate::phase::{ + HasSystem as PhaseHasSystem, + POST_UPDATE as POST_UPDATE_PHASE, + PRE_UPDATE as PRE_UPDATE_PHASE, + Phase, + START as START_PHASE, + UPDATE as UPDATE_PHASE, +}; +use crate::query::flexible::Query as FlexibleQuery; +use crate::query::{ + MAX_TERM_CNT as QUERY_MAX_TERM_CNT, + TermWithFieldTuple as QueryTermWithFieldTuple, + TermWithoutFieldTuple as QueryTermWithoutFieldTuple, + Terms as QueryTerms, + TermsBuilderInterface, +}; +use crate::sole::{Single, Sole}; +use crate::stats::Stats; +use crate::system::observer::{Observer, WrapperComponent as ObserverWrapperComponent}; +use crate::system::{Callbacks, Metadata as SystemMetadata, System, SystemComponent}; +use crate::uid::{Kind as UidKind, Uid}; + +pub mod actions; +pub mod component; +pub mod entity; +pub mod error; +pub mod event; +pub mod extension; +pub mod pair; +pub mod phase; +pub mod query; +pub mod sole; +pub mod stats; +pub mod system; +pub mod tuple; +pub mod uid; +pub mod util; + +mod lock; + +pub use engine_ecs_macros::{Component, Sole}; + +pub use crate::query::Query; + +#[derive(Debug)] +pub struct World +{ + data: WorldData, + stop: AtomicBool, + is_first_tick: AtomicBool, + error_handler: ErrorHandler, +} + +impl World +{ + #[must_use] + pub fn new() -> Self + { + let mut world = Self { + data: WorldData::default(), + stop: AtomicBool::new(false), + is_first_tick: AtomicBool::new(false), + error_handler: err_handler_panic, + }; + + crate::phase::spawn_entities(&mut world); + + world.add_sole(Stats::default()).ok(); + + world + } + + pub fn set_err_handler(&mut self, err_handler: ErrorHandler) + { + self.error_handler = err_handler; + } + + /// Creates a entity with the given components. A new unique [`Uid`] will be generated + /// for this entity. + pub fn create_entity<Comps>(&mut self, components: Comps) -> Uid + where + Comps: ComponentSequence, + { + let entity_uid = Uid::new_unique(UidKind::Entity); + + self.create_entity_with_uid(entity_uid, components); + + entity_uid + } + + /// Creates a entity with the given components. The entity will have the specified + /// [`Uid`]. + #[tracing::instrument(skip_all)] + pub fn create_entity_with_uid<Comps>(&mut self, entity_uid: Uid, components: Comps) + where + Comps: ComponentSequence, + { + self.create_ent(entity_uid, components.into_parts_array()); + } + + pub fn add_component(&mut self, entity_id: Uid, component_parts: ComponentParts) + { + Self::add_entity_components( + entity_id, + [component_parts], + &mut self.data.component_storage, + &EventSubmitter::new(&self.data.new_events), + ); + } + + pub fn create_declared_entity(&mut self, entity_decl: &EntityDeclaration) + { + entity_decl.create(self); + } + + /// Adds a globally shared singleton value. + /// + /// # Errors + /// Returns `Err` if this [`Sole`] has already been added. + pub fn add_sole<SoleT>(&mut self, sole: SoleT) -> Result<(), SoleAlreadyExistsError> + where + SoleT: Sole, + { + self.data.sole_storage.insert(sole) + } + + pub fn register_observer<'this, SystemImpl, ObserverT>( + &'this mut self, + observer: ObserverT, + ) where + ObserverT: Observer<'this, SystemImpl>, + { + let (wrapper_comp, mut system_callbacks) = observer.finish_observer(); + + let ent_id = Uid::new_unique(UidKind::Entity); + + self.create_ent( + ent_id, + [wrapper_comp.into_parts()].into_iter().chain( + ObserverT::observed_events() + .into_iter() + .map(IntoComponentParts::into_parts), + ), + ); + + system_callbacks.on_created(self, SystemMetadata { ent_id }); + } + + pub fn register_system<'this, SystemImpl>( + &'this mut self, + phase_euid: Uid, + system: impl System<'this, SystemImpl>, + ) + { + let (type_erased_system, mut system_callbacks) = system.finish(); + + let system_ent_id = + self.create_entity((SystemComponent { system: type_erased_system },)); + + system_callbacks.on_created(self, SystemMetadata { ent_id: system_ent_id }); + + self.create_entity_with_uid( + phase_euid, + (Pair::builder() + .relation::<PhaseHasSystem>() + .target_id(system_ent_id) + .build(),), + ); + } + + /// Adds a extensions. + pub fn add_extension(&mut self, extension: impl Extension) + { + let extension_collector = ExtensionCollector::new(self); + + extension.collect(extension_collector); + } + + pub fn query<FieldTerms, FieldlessTerms>( + &self, + ) -> Query<'_, FieldTerms, FieldlessTerms> + where + FieldTerms: QueryTermWithFieldTuple, + FieldlessTerms: QueryTermWithoutFieldTuple, + { + Query::new(self) + } + + pub fn flexible_query<const MAX_TERM_CNT: usize>( + &self, + terms: QueryTerms<MAX_TERM_CNT>, + ) -> FlexibleQuery<'_, MAX_TERM_CNT> + { + FlexibleQuery::new(self, terms) + } + + pub fn get_entity(&self, entity_id: Uid) -> Option<EntityHandle<'_>> + { + let archetype = self + .data + .component_storage + .get_entity_archetype(entity_id)?; + + let Some(entity) = archetype.get_entity_by_id(entity_id) else { + unreachable!("Should exist since archetype was found by entity id"); + }; + + Some(EntityHandle::new(archetype, entity, self)) + } + + pub fn get_sole<SoleT: Sole>(&self) -> Option<Single<'_, SoleT>> + { + Some(Single::new(self.data.sole_storage.get::<SoleT>()?)) + } + + pub fn event_submitter(&self) -> EventSubmitter<'_> + { + EventSubmitter::new(&self.data.new_events) + } + + /// Performs a single tick. + /// # Panics + /// Will panic if mutable internal lock cannot be acquired. + pub fn step(&mut self) -> StepResult + { + if self.stop.load(Ordering::Relaxed) { + return StepResult::Stop; + } + + if self + .is_first_tick + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + let Some(start_phase_entity) = self.get_entity(*START_PHASE) else { + unreachable!(); + }; + + self.run_phase_systems(&start_phase_entity); + } + + self.perform_phases(); + + self.emit_new_events(); + + self.data.component_storage.create_imaginary_archetypes(); + + self.perform_queued_actions(); + + if self.stop.load(Ordering::Relaxed) { + return StepResult::Stop; + } + + let Some(mut stats) = self.get_sole::<Stats>() else { + unreachable!(); // Reason: is added in World::new + }; + + stats.current_tick += 1; + + StepResult::Continue + } + + /// Starts a loop which calls [`Self::step`] until the world is stopped. + pub fn start_loop(&mut self) + { + while let StepResult::Continue = self.step() {} + } + + #[cfg(feature = "vizoxide")] + pub fn create_vizoxide_archetype_graph( + &self, + name: impl AsRef<str>, + ) -> Result<vizoxide::Graph, vizoxide::GraphvizError> + { + use std::borrow::Cow; + + use crate::component::storage::{ + VizoxideArchetypeGraphEdgeKind, + VizoxideArchetypeGraphParams, + }; + + self.data.component_storage.create_vizoxide_archetype_graph( + name, + VizoxideArchetypeGraphParams { + create_node_name: |archetype, _| { + Cow::Owned(format!( + "[{}]", + archetype + .component_ids_sorted() + .into_iter() + .map(|comp_id| comp_id.to_string()) + .collect::<Vec<_>>() + .join(", ") + )) + }, + create_node_cb: |_archetype, archetype_metadata, node_builder| { + if archetype_metadata.is_imaginary { + return node_builder.attribute("shape", "ellipse"); + } + + node_builder.attribute("shape", "box") + }, + create_edge_cb: |_, _, edge_kind, edge_builder| { + edge_builder.attribute( + "color", + match edge_kind { + VizoxideArchetypeGraphEdgeKind::Add => "green", + VizoxideArchetypeGraphEdgeKind::Remove => "red", + }, + ) + }, + }, + ) + } + + #[tracing::instrument(skip_all)] + fn create_ent( + &mut self, + entity_uid: Uid, + components: impl IntoIterator<Item = ComponentParts>, + ) + { + debug_assert_eq!(entity_uid.kind(), UidKind::Entity); + + if let Err(EntityAlreadyExistsError) = + self.data.component_storage.create_entity(entity_uid) + { + // This is fine + } + + Self::add_entity_components( + entity_uid, + components, + &mut self.data.component_storage, + &EventSubmitter::new(&self.data.new_events), + ); + } + + fn run_phase_systems(&self, phase_entity: &EntityHandle<'_>) + { + // The phase's systems are retrieved this way so that the order they are + // run is the same order as they were registered, even if they have local + // components. + for system_entity in phase_entity + .get_wildcard_pair_matches::<PhaseHasSystem, Wildcard>() + .into_iter() + .filter_map(|phase_has_system| phase_has_system.get_target_ent()) + { + let Some(system) = system_entity.get::<SystemComponent>() else { + cold_path(); + continue; + }; + + // SAFETY: The world lives long enough + if let Err(err) = unsafe { + system + .system + .run(self, SystemMetadata { ent_id: system_entity.uid() }) + } { + cold_path(); + + (self.error_handler)( + err, + ErrorMetadata { + source_name: system.system.name(), + source_kind: ErrorSourceKind::System, + }, + ) + } + } + } + + fn perform_child_phases(&self, parent_phase_euid: Uid) + { + let phase_query = self.flexible_query( + QueryTerms::<2>::builder() + .with_required([ + Phase::id(), + Pair::builder() + .relation::<ChildOf>() + .target_id(parent_phase_euid) + .build() + .id(), + ]) + .build(), + ); + + for child_phase_entity in &phase_query { + self.run_phase_systems(&child_phase_entity); + self.perform_child_phases(child_phase_entity.uid()); + } + } + + fn perform_single_phase(&self, phase_entity_id: Uid) + { + let Some(phase_entity) = self.get_entity(phase_entity_id) else { + unreachable!(); + }; + + self.run_phase_systems(&phase_entity); + self.perform_child_phases(phase_entity_id); + } + + fn perform_phases(&self) + { + self.perform_single_phase(*PRE_UPDATE_PHASE); + self.perform_single_phase(*UPDATE_PHASE); + self.perform_single_phase(*POST_UPDATE_PHASE); + } + + fn emit_new_events(&self) + { + loop { + let new_events = { + let mut new_events_lock = self + .data + .new_events + .write_nonblock() + .expect("Failed to acquire read-write lock to new events"); + + if new_events_lock.is_empty() { + break; + } + + new_events_lock.take() + }; + + for (event_id, event_matches) in new_events { + self.emit_event_observers( + event_id, + &EmittedEvent { + event: event_id, + match_ids: &event_matches.match_ids, + }, + ); + } + } + } + + #[tracing::instrument(skip_all)] + fn perform_queued_actions(&mut self) + { + let mut action_queue_lock = self + .data + .action_queue + .queue + .write_nonblock() + .unwrap_or_else(|err| { + panic!("Failed to take read-write action queue lock: {err}",); + }); + + for action in action_queue_lock.drain(..) { + match action { + Action::Spawn(new_entity_uid, components) => { + if let Err(err) = + self.data.component_storage.create_entity(new_entity_uid) + { + tracing::warn!("Failed to create entity: {err}"); + continue; + } + + Self::add_entity_components( + new_entity_uid, + components, + &mut self.data.component_storage, + &EventSubmitter::new(&self.data.new_events), + ); + } + Action::Despawn(entity_uid) => { + if let Err(err) = + self.data.component_storage.remove_entity(entity_uid) + { + tracing::error!("Failed to despawn entity: {err}"); + } + } + Action::AddComponents(entity_uid, components) => { + Self::add_entity_components( + entity_uid, + components, + &mut self.data.component_storage, + &EventSubmitter::new(&self.data.new_events), + ); + } + Action::RemoveComponents(entity_uid, component_ids) => { + Self::remove_entity_components( + entity_uid, + component_ids, + &mut self.data.component_storage, + ); + } + Action::Stop => { + self.stop.store(true, Ordering::Relaxed); + } + } + } + } + + fn add_entity_components( + entity_uid: Uid, + components: impl IntoIterator<Item = ComponentParts>, + component_storage: &mut ComponentStorage, + event_submitter: &EventSubmitter<'_>, + ) + { + let component_iter = components.into_iter(); + + for component_parts in component_iter { + let comp_id = component_parts.id(); + + let comp_name = component_parts.name(); + + if let Err(err) = component_storage.add_entity_component( + entity_uid, + (comp_id, comp_name, component_parts.into_data()), + ) { + tracing::error!("Failed to add component {comp_name} to entity: {err}"); + continue; + } + + if comp_id.kind() == UidKind::Pair { + continue; + } + + event_submitter.submit_event( + &Pair::builder() + .relation::<Added>() + .target_id(comp_id) + .build(), + entity_uid, + ); + } + } + + fn remove_entity_components( + entity_uid: Uid, + component_ids: impl IntoIterator<Item = Uid>, + component_storage: &mut ComponentStorage, + ) + { + let component_id_iter = component_ids.into_iter(); + + for component_id in component_id_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_observers(&self, event_id: Uid, emitted_event: &EmittedEvent<'_>) + { + assert_eq!(event_id.kind(), UidKind::Pair); + + let query = Query::<(&ObserverWrapperComponent,)>::from_flexible_query( + self.flexible_query( + QueryTerms::<QUERY_MAX_TERM_CNT>::builder() + .with_required([ObserverWrapperComponent::id(), event_id]) + .build(), + ), + ); + + for (observer_ent_id, (observer,)) in query.iter_with_euids() { + if let Err(err) = unsafe { + observer.run( + self, + SystemMetadata { ent_id: observer_ent_id }, + emitted_event.clone(), + ) + } { + cold_path(); + + (self.error_handler)( + err, + ErrorMetadata { + source_name: observer.name(), + source_kind: ErrorSourceKind::Observer, + }, + ) + } + } + } +} + +impl Default for World +{ + fn default() -> Self + { + Self::new() + } +} + +/// The result of calling [`World::step`]. +pub enum StepResult +{ + /// Another step can be made. + Continue, + + /// The world have been stopped so no step can be made again. + Stop, +} + +#[derive(Debug, Default)] +struct WorldData +{ + component_storage: ComponentStorage, + sole_storage: SoleStorage, + action_queue: Rc<ActionQueue>, + new_events: Lock<NewEvents>, +} + +#[derive(Debug, Clone)] +pub struct EntityComponentRef<'a> +{ + component_id: Uid, + component: &'a ArchetypeEntityComponent, + entity_id: Uid, +} + +impl<'a> EntityComponentRef<'a> +{ + fn component(&self) -> &'a Lock<Box<dyn Any>> + { + self.component.component() + } + + #[must_use] + pub fn id(&self) -> Uid + { + self.component_id + } + + #[must_use] + pub fn entity_id(&self) -> Uid + { + self.entity_id + } + + fn new(component_id: Uid, comp: &'a ArchetypeEntityComponent, entity_id: Uid) + -> Self + { + Self { + component_id, + component: comp, + entity_id, + } + } +} + +#[derive(Debug, Default)] +struct ActionQueue +{ + queue: Lock<Vec<Action>>, +} + +impl ActionQueue +{ + fn push(&self, action: Action) + { + self.queue + .write_nonblock() + .expect("Failed to aquire read-write lock to action queue") + .push(action); + } +} + +#[derive(Debug, thiserror::Error)] +#[error("Sole {0} already exists")] +pub struct SoleAlreadyExistsError(pub &'static str); + +#[derive(Debug)] +struct StoredSole +{ + sole: Arc<Lock<Box<dyn Sole>>>, + drop_last: bool, +} + +#[derive(Debug, Default)] +struct SoleStorage +{ + storage: HashMap<TypeId, ManuallyDrop<StoredSole>>, +} + +impl SoleStorage +{ + fn get<SoleT: Sole>(&self) -> Option<&Arc<Lock<Box<dyn Sole>>>> + { + self.storage + .get(&TypeId::of::<SoleT>()) + .map(|sole| &sole.sole) + } + + fn insert<SoleT: Sole>(&mut self, sole: SoleT) -> Result<(), SoleAlreadyExistsError> + { + let sole_type_id = TypeId::of::<SoleT>(); + + if self.storage.contains_key(&sole_type_id) { + return Err(SoleAlreadyExistsError(type_name::<SoleT>())); + } + + let drop_last = sole.drop_last(); + + // TODO: Reconsider this maybe? + #[allow(clippy::arc_with_non_send_sync)] + self.storage.insert( + sole_type_id, + ManuallyDrop::new(StoredSole { + sole: Arc::new(Lock::new(Box::new(sole), type_name::<SoleT>())), + drop_last, + }), + ); + + Ok(()) + } +} + +impl Drop for SoleStorage +{ + fn drop(&mut self) + { + let mut soles_to_drop_last = Vec::new(); + + for sole in self.storage.values_mut() { + if sole.drop_last { + soles_to_drop_last.push(sole); + continue; + } + + unsafe { + ManuallyDrop::drop(sole); + } + } + + for sole in &mut soles_to_drop_last { + unsafe { + ManuallyDrop::drop(sole); + } + } + } +} |
