From 8022e8998290b067b8aa0cb9cba8ba410826bdab Mon Sep 17 00:00:00 2001 From: HampusM Date: Thu, 21 May 2026 17:55:20 +0200 Subject: chore: rename ecs* crates to engine-ecs* --- engine-ecs/src/lib.rs | 773 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 773 insertions(+) create mode 100644 engine-ecs/src/lib.rs (limited to 'engine-ecs/src/lib.rs') 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(&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(&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(&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::() + .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( + &self, + ) -> Query<'_, FieldTerms, FieldlessTerms> + where + FieldTerms: QueryTermWithFieldTuple, + FieldlessTerms: QueryTermWithoutFieldTuple, + { + Query::new(self) + } + + pub fn flexible_query( + &self, + terms: QueryTerms, + ) -> FlexibleQuery<'_, MAX_TERM_CNT> + { + FlexibleQuery::new(self, terms) + } + + pub fn get_entity(&self, entity_id: Uid) -> Option> + { + 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(&self) -> Option> + { + Some(Single::new(self.data.sole_storage.get::()?)) + } + + 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::() 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, + ) -> Result + { + 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::>() + .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, + ) + { + 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::() + .into_iter() + .filter_map(|phase_has_system| phase_has_system.get_target_ent()) + { + let Some(system) = system_entity.get::() 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::() + .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, + 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::() + .target_id(comp_id) + .build(), + entity_uid, + ); + } + } + + fn remove_entity_components( + entity_uid: Uid, + component_ids: impl IntoIterator, + 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::::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, + new_events: Lock, +} + +#[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> + { + 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>, +} + +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>>, + drop_last: bool, +} + +#[derive(Debug, Default)] +struct SoleStorage +{ + storage: HashMap>, +} + +impl SoleStorage +{ + fn get(&self) -> Option<&Arc>>> + { + self.storage + .get(&TypeId::of::()) + .map(|sole| &sole.sole) + } + + fn insert(&mut self, sole: SoleT) -> Result<(), SoleAlreadyExistsError> + { + let sole_type_id = TypeId::of::(); + + if self.storage.contains_key(&sole_type_id) { + return Err(SoleAlreadyExistsError(type_name::())); + } + + 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::())), + 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); + } + } + } +} -- cgit v1.2.3-18-g5258