summaryrefslogtreecommitdiff
path: root/engine-ecs/src/actions.rs
diff options
context:
space:
mode:
Diffstat (limited to 'engine-ecs/src/actions.rs')
-rw-r--r--engine-ecs/src/actions.rs384
1 files changed, 384 insertions, 0 deletions
diff --git a/engine-ecs/src/actions.rs b/engine-ecs/src/actions.rs
new file mode 100644
index 0000000..d079a85
--- /dev/null
+++ b/engine-ecs/src/actions.rs
@@ -0,0 +1,384 @@
+use std::borrow::Cow;
+
+use crate::bundle::Bundle;
+use crate::component::{Component, IntoParts, Parts as ComponentParts};
+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.
+ ///
+ /// # Panics
+ /// This function will panic if any component in `bundle` is a [`Sole`].
+ pub fn spawn<BundleT: Bundle>(&mut self, bundle: BundleT) -> Uid
+ {
+ let new_entity_uid = Uid::new_unique();
+
+ let components_parts = bundle.iter_component_parts().collect::<Vec<_>>();
+
+ if let Some(ComponentParts { name: sole_name, .. }) = components_parts
+ .iter()
+ .find(|comp_parts| comp_parts.is_sole)
+ {
+ panic!("Cannot spawn entity with sole component '{sole_name}'");
+ }
+
+ self.action_queue
+ .push(Action::Spawn(new_entity_uid, components_parts));
+
+ 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`.
+ ///
+ /// # Panics
+ /// This function will panic if any component in `bundle` is a [`Sole`].
+ pub fn spawn_named<BundleT: Bundle>(
+ &mut self,
+ name: impl Into<Cow<'static, str>>,
+ bundle: BundleT,
+ ) -> Uid
+ {
+ let new_entity_uid = Uid::new_unique();
+
+ let components_parts = bundle
+ .iter_component_parts()
+ .chain([EntityName { name: name.into() }.into_parts()])
+ .collect::<Vec<_>>();
+
+ if let Some(ComponentParts { name: sole_name, .. }) = components_parts
+ .iter()
+ .find(|comp_parts| comp_parts.is_sole)
+ {
+ panic!("Cannot spawn entity with sole component '{sole_name}'");
+ }
+
+ self.action_queue
+ .push(Action::Spawn(new_entity_uid, components_parts));
+
+ 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.
+ ///
+ /// # Panics
+ /// This function will panic if any component in `bundle` is a [`Sole`].
+ pub fn add_components<BundleT: Bundle>(&mut self, entity_uid: Uid, bundle: BundleT)
+ {
+ debug_assert!(!entity_uid.is_pair());
+
+ let components_parts = bundle.iter_component_parts().collect::<Vec<_>>();
+
+ if components_parts.is_empty() {
+ return;
+ }
+
+ if let Some(ComponentParts { name: sole_name, .. }) = components_parts
+ .iter()
+ .find(|comp_parts| comp_parts.is_sole)
+ {
+ panic!("Cannot spawn entity with sole component '{sole_name}'");
+ }
+
+ self.action_queue
+ .push(Action::AddComponents(entity_uid, components_parts));
+ }
+
+ /// 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,
+ 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,
+ 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,
+ 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,
+ 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,
+}