diff options
Diffstat (limited to 'engine-ecs/src/actions.rs')
| -rw-r--r-- | engine-ecs/src/actions.rs | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/engine-ecs/src/actions.rs b/engine-ecs/src/actions.rs new file mode 100644 index 0000000..9ca9198 --- /dev/null +++ b/engine-ecs/src/actions.rs @@ -0,0 +1,392 @@ +use std::borrow::Cow; + +use crate::component::{ + Component, + IntoParts, + Parts as ComponentParts, + Sequence as ComponentSequence, +}; +use crate::entity::Name as EntityName; +use crate::event::component::Removed; +use crate::pair::Pair; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; +use crate::uid::{Uid, WithUidTuple}; +use crate::{ActionQueue, World}; + +/// Used to to queue up actions for a [`World`] to perform. +#[derive(Debug)] +pub struct Actions<'world> +{ + action_queue: &'world ActionQueue, + world: Option<&'world World>, +} + +impl Actions<'_> +{ + /// Queues up a entity to spawn at the end of the current tick, returning the [`Uid`] + /// that the entity will have. + pub fn spawn<Comps: ComponentSequence>(&mut self, components: Comps) -> Uid + { + let new_entity_uid = Uid::new_unique(); + + let components_parts = components.into_parts_array(); + + if let Some(comp_parts) = components_parts + .as_ref() + .iter() + .find(|comp_parts| comp_parts.is_sole) + { + panic!( + "Cannot spawn entity with sole component {}", + comp_parts.name + ); + } + + self.action_queue + .push(Action::Spawn(new_entity_uid, components_parts.into())); + + new_entity_uid + } + + /// Queues up a entity to spawn at the end of the current tick, returning the [`Uid`] + /// that the entity will have. + /// + /// In addition to the given components, the entity will have a [`EntityName`] + /// component containing `name`. + pub fn spawn_named<Comps: ComponentSequence>( + &mut self, + name: impl Into<Cow<'static, str>>, + components: Comps, + ) -> Uid + { + let new_entity_uid = Uid::new_unique(); + + let components_parts = components.into_parts_array(); + + if let Some(comp_parts) = components_parts + .as_ref() + .iter() + .find(|comp_parts| comp_parts.is_sole) + { + panic!( + "Cannot spawn entity with sole component {}", + comp_parts.name + ); + } + + self.action_queue.push(Action::Spawn( + new_entity_uid, + components_parts + .into_iter() + .chain([EntityName { name: name.into() }.into_parts()]) + .collect(), + )); + + new_entity_uid + } + + /// Queues up despawning a entity at the end of the current tick. + pub fn despawn(&mut self, entity_uid: Uid) + { + debug_assert!(!entity_uid.is_pair()); + + let Some(world) = self.world else { + self.action_queue.push(Action::Despawn(entity_uid)); + return; + }; + + let Some(ent) = world.get_entity(entity_uid) else { + tracing::warn!("Cannot entity that doesn't exist"); + return; + }; + + // TODO: Submit all events with a single function call to reduce overhead + for comp_id in ent.component_ids() { + if comp_id.is_pair() { + continue; + } + + world.event_submitter().submit_event( + &Pair::builder() + .relation::<Removed>() + .target_id(comp_id) + .build(), + entity_uid, + ); + } + + self.action_queue.push(Action::Despawn(entity_uid)); + } + + /// Queues up adding component(s) to a entity at the end of the current tick. + pub fn add_components<Comps>(&mut self, entity_uid: Uid, components: Comps) + where + Comps: ComponentSequence, + { + debug_assert!(!entity_uid.is_pair()); + + if components.cnt() == 0 { + return; + } + + let components_parts = components.into_parts_array(); + + if let Some(comp_parts) = components_parts + .as_ref() + .iter() + .find(|comp_parts| comp_parts.is_sole) + { + panic!("Cannot add sole component {} to an entity", comp_parts.name); + } + + self.action_queue + .push(Action::AddComponents(entity_uid, components_parts.into())); + } + + /// Queues up removing component(s) from a entity at the end of the current tick. + #[tracing::instrument(skip(self, component_ids))] + pub fn remove_components( + &mut self, + entity_uid: Uid, + component_ids: impl IntoIterator<Item = Uid>, + ) + { + debug_assert!(!entity_uid.is_pair()); + + let mut component_ids = component_ids.into_iter().peekable(); + + if component_ids.peek().is_none() { + return; + } + + let Some(world) = self.world else { + self.action_queue.push(Action::RemoveComponents( + entity_uid, + component_ids.collect(), + )); + return; + }; + + let Some(ent) = world.get_entity(entity_uid) else { + tracing::warn!("Cannot remove components from entity that doesn't exist"); + return; + }; + + let component_ids = component_ids + .filter(|comp_id| ent.has_component(*comp_id)) + .collect::<Vec<_>>(); + + if component_ids.is_empty() { + return; + } + + // TODO: Submit all events with a single function call to reduce overhead + for comp_id in &component_ids { + if comp_id.is_pair() { + continue; + } + + world.event_submitter().submit_event( + &Pair::builder() + .relation::<Removed>() + .target_id(*comp_id) + .build(), + entity_uid, + ); + } + + self.action_queue + .push(Action::RemoveComponents(entity_uid, component_ids)); + } + + /// Queues up removing component(s) from a entity at the end of the current tick. + pub fn remove_comps<Ids: WithUidTuple>(&mut self, entity_uid: Uid) + { + self.remove_components(entity_uid, Ids::uids()); + } + + /// Queues up setting the target of a entity's pair at the end of the current tick. + /// If the pair does not exist in the entity, it is still added to the entity with the + /// new target. + #[tracing::instrument( + skip_all, + fields(entity=%entity_id, pair=%pair.id(), new_target=%new_target_id) + )] + pub fn set_pair_target( + &mut self, + entity_id: Uid, + pair: Pair<Uid, Uid>, + new_target_id: Uid, + ) + { + debug_assert!(!entity_id.is_pair()); + debug_assert!(!new_target_id.is_pair()); + + if let Some(world) = self.world { + if world.get_entity(entity_id).is_none() { + tracing::error!("Entity does not exist"); + return; + } + } + + let pair_id = pair.id(); + + self.action_queue.push(Action::SetPair { + entity_id: entity_id, + pair_id: pair_id, + new_pair: Pair::builder() + .relation_id(pair_id.relation()) + .target_id(new_target_id) + .build() + .into_parts(), + }); + } + + /// Queues up setting the target of a entity's pair at the end of the current tick. + /// If the pair does not exist in the entity, it is still added to the entity with the + /// new target. + #[tracing::instrument( + skip_all, + fields(entity=%entity_id, pair=%pair.id(), new_target=%NewTarget::id()) + )] + pub fn set_pair_target_and_data<NewTarget>( + &mut self, + entity_id: Uid, + pair: Pair<Uid, Uid>, + new_target: NewTarget, + ) where + NewTarget: Component, + { + debug_assert!(!entity_id.is_pair()); + + if let Some(world) = self.world { + if world.get_entity(entity_id).is_none() { + tracing::error!("Entity does not exist"); + return; + } + } + + let pair_id = pair.id(); + + self.action_queue.push(Action::SetPair { + entity_id: entity_id, + pair_id: pair_id, + new_pair: Pair::builder() + .relation_id(pair_id.relation()) + .target_as_data(new_target) + .build() + .into_parts(), + }); + } + + /// Queues up setting the relation of a entity's pair at the end of the current tick. + /// If the pair does not exist in the entity, it is still added to the entity with the + /// new relation. + #[tracing::instrument( + skip_all, + fields(entity=%entity_id, pair=%pair.id(), new_relation=%new_relation_id) + )] + pub fn set_pair_relation( + &mut self, + entity_id: Uid, + pair: Pair<Uid, Uid>, + new_relation_id: Uid, + ) + { + debug_assert!(!entity_id.is_pair()); + debug_assert!(!new_relation_id.is_pair()); + + if let Some(world) = self.world { + if world.get_entity(entity_id).is_none() { + tracing::error!("Entity does not exist"); + return; + } + } + + let pair_id = pair.id(); + + self.action_queue.push(Action::SetPair { + entity_id: entity_id, + pair_id: pair_id, + new_pair: Pair::builder() + .relation_id(new_relation_id) + .target_id(pair_id.target()) + .build() + .into_parts(), + }); + } + + /// Queues up setting the relation of a entity's pair at the end of the current tick. + /// If the pair does not exist in the entity, it is still added to the entity with the + /// new relation. + #[tracing::instrument( + skip_all, + fields(entity=%entity_id, pair=%pair.id(), new_relation=%NewRelation::id()) + )] + pub fn set_pair_relation_and_data<NewRelation>( + &mut self, + entity_id: Uid, + pair: Pair<Uid, Uid>, + new_relation: NewRelation, + ) where + NewRelation: Component, + { + debug_assert!(!entity_id.is_pair()); + + if let Some(world) = self.world { + if world.get_entity(entity_id).is_none() { + tracing::error!("Entity does not exist"); + return; + } + } + + let pair_id = pair.id(); + + self.action_queue.push(Action::SetPair { + entity_id: entity_id, + pair_id: pair_id, + new_pair: Pair::builder() + .relation_as_data(new_relation) + .target_id(pair_id.target()) + .build() + .into_parts(), + }); + } + + /// Stops the [`World`]. The world will finish the current tick and that tick will be + /// the last. + pub fn stop(&mut self) + { + self.action_queue.push(Action::Stop); + } +} + +impl<'world> SystemParam<'world> for Actions<'world> +{ + type Input = (); + + fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self + { + Self { + action_queue: &world.data.action_queue, + world: Some(world), + } + } +} + +/// A action for a [`System`] to perform. +#[derive(Debug)] +pub(crate) enum Action +{ + Spawn(Uid, Vec<ComponentParts>), + Despawn(Uid), + AddComponents(Uid, Vec<ComponentParts>), + RemoveComponents(Uid, Vec<Uid>), + SetPair + { + entity_id: Uid, + pair_id: Uid, + new_pair: ComponentParts, + }, + Stop, +} |
