From 5c9113431ea22c53cc59324c93ec3dc6efdfe926 Mon Sep 17 00:00:00 2001 From: HampusM Date: Wed, 20 Aug 2025 14:17:14 +0200 Subject: feat(ecs): add support for pairs with target component as data --- ecs/examples/component_relationship.rs | 56 ++++++++++ ecs/examples/relationship.rs | 3 +- ecs/src/lib.rs | 4 +- ecs/src/pair.rs | 198 ++++++++++++++++++++++++++------- ecs/src/uid.rs | 13 +-- 5 files changed, 217 insertions(+), 57 deletions(-) create mode 100644 ecs/examples/component_relationship.rs diff --git a/ecs/examples/component_relationship.rs b/ecs/examples/component_relationship.rs new file mode 100644 index 0000000..4453e3a --- /dev/null +++ b/ecs/examples/component_relationship.rs @@ -0,0 +1,56 @@ +use ecs::pair::Pair; +use ecs::phase::START as START_PHASE; +use ecs::{Component, Query, World}; + +#[derive(Component)] +struct Person +{ + name: String, +} + +fn print_dog_likers(query: Query<(&Person, Pair)>) +{ + for (person, liked_dogs) in &query { + println!( + "{} likes {} dogs!", + person.name, + if liked_dogs.large { "large" } else { "small" }, + ); + } +} + +#[derive(Component)] +struct Likes; + +#[derive(Component)] +struct Cats; + +#[derive(Component)] +struct Dogs +{ + large: bool, +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*START_PHASE, print_dog_likers); + + world.create_entity(( + Person { name: "Irving".to_string() }, + Pair::new_with_comp_target::(Dogs { large: true }), + )); + + world.create_entity(( + Person { name: "Mark".to_string() }, + Pair::new_with_comp_target::(Cats), + )); + + world.create_entity(( + Person { name: "Helena".to_string() }, + Pair::new_with_comp_target::(Dogs { large: false }), + )); + + world.step(); +} diff --git a/ecs/examples/relationship.rs b/ecs/examples/relationship.rs index b607398..f99d090 100644 --- a/ecs/examples/relationship.rs +++ b/ecs/examples/relationship.rs @@ -1,6 +1,5 @@ -use ecs::pair::Pair; +use ecs::pair::{Pair, Wildcard}; use ecs::phase::START as START_PHASE; -use ecs::uid::Wildcard; use ecs::{Component, Query, World}; #[derive(Component)] diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs index 979b517..fa31460 100644 --- a/ecs/src/lib.rs +++ b/ecs/src/lib.rs @@ -25,7 +25,7 @@ use crate::event::component::{ }; use crate::extension::{Collector as ExtensionCollector, Extension}; use crate::lock::Lock; -use crate::pair::{ChildOf, DependsOn, Pair}; +use crate::pair::{ChildOf, DependsOn, Pair, Wildcard}; use crate::phase::{Phase, START as START_PHASE}; use crate::query::flexible::Query as FlexibleQuery; use crate::query::term::Without; @@ -39,7 +39,7 @@ use crate::query::{ use crate::sole::Sole; use crate::stats::Stats; use crate::system::{System, SystemComponent}; -use crate::uid::{Kind as UidKind, Uid, Wildcard}; +use crate::uid::{Kind as UidKind, Uid}; pub mod actions; pub mod component; diff --git a/ecs/src/pair.rs b/ecs/src/pair.rs index 4ff4995..77b6da7 100644 --- a/ecs/src/pair.rs +++ b/ecs/src/pair.rs @@ -1,6 +1,12 @@ +use std::any::type_name; use std::convert::Infallible; -use crate::component::{IntoParts as IntoComponentParts, Parts as ComponentParts}; +use crate::component::{ + Handle as ComponentHandle, + HandleMut as ComponentHandleMut, + IntoParts as IntoComponentParts, + Parts as ComponentParts, +}; use crate::entity::{ Handle as EntityHandle, MatchingComponentIter as EntityMatchingComponentIter, @@ -10,22 +16,22 @@ use crate::query::{ TermsBuilder as QueryTermsBuilder, TermsBuilderInterface, }; -use crate::uid::{PairParams as UidPairParams, Uid, Wildcard, With as WithUid}; +use crate::uid::{PairParams as UidPairParams, Uid, With as WithUid}; use crate::{Component, World}; #[derive(Debug)] -pub struct Pair +pub struct Pair { - relation: RelationElem, - target: TargetElem, + relation: Relation, + target: Target, } impl Pair { #[must_use] - pub fn new(target: Uid) -> Self + pub fn new(target: Uid) -> Self { - Self { relation: Relation::uid(), target } + Self { relation: Relation::id(), target } } #[must_use] @@ -38,6 +44,20 @@ impl Pair } } +impl Pair +where + Target: Component, +{ + /// Returns a new pair that contains the target component as data. + pub fn new_with_comp_target(target_component: Target) -> Self + { + Self { + relation: Relation::uid(), + target: target_component, + } + } +} + impl IntoComponentParts for Pair { fn into_parts(self) -> ComponentParts @@ -46,12 +66,94 @@ impl IntoComponentParts for Pair } } -impl QueryTermWithField for Pair +impl IntoComponentParts for Pair +where + Target: Component, +{ + fn into_parts(self) -> ComponentParts + { + let id = Uid::new_pair(&UidPairParams { + relation: self.relation, + target: Target::id(), + }); + + ComponentParts::builder() + .name("Pair") + .build(id, self.target) + } +} + +impl QueryTermWithField for Pair +where + Relation: Component, + Target: Component, +{ + type Field<'a> = ComponentHandle<'a, Target>; + + fn apply_to_terms_builder( + terms_builder: &mut QueryTermsBuilder, + ) + { + terms_builder.with_required([Self::uid()]); + } + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + _world: &'world World, + ) -> Self::Field<'world> + { + let target_component = entity_handle + .get_matching_components(Self::uid()) + .next() + .expect("Not possible"); + + Self::Field::from_entity_component_ref(target_component).unwrap_or_else(|err| { + panic!( + "Creating handle to target component {} failed: {err}", + type_name::() + ); + }) + } +} + +impl QueryTermWithField for Pair +where + Relation: Component, + Target: Component, +{ + type Field<'a> = ComponentHandleMut<'a, Target>; + + fn apply_to_terms_builder( + terms_builder: &mut QueryTermsBuilder, + ) + { + terms_builder.with_required([Self::uid()]); + } + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + _world: &'world World, + ) -> Self::Field<'world> + { + let target_component = entity_handle + .get_matching_components(Self::uid()) + .next() + .expect("Not possible"); + + Self::Field::from_entity_component_ref(target_component).unwrap_or_else(|err| { + panic!( + "Creating handle to target component {} failed: {err}", + type_name::() + ); + }) + } +} + +impl QueryTermWithField for Pair where - Relation: WithUid, - Target: WithUid, + Relation: Component, { - type Field<'a> = Handle<'a>; + type Field<'a> = EntityTargetHandle<'a>; fn apply_to_terms_builder( terms_builder: &mut QueryTermsBuilder, @@ -70,17 +172,17 @@ where .next() .expect("Not possible"); - Handle { + EntityTargetHandle { world, pair_uid: first_matching_comp.id(), } } } -impl WithUid for Pair +impl WithUid for Pair where - Relation: WithUid, - Target: WithUid, + Relation: Component, + Target: Component, { fn uid() -> Uid { @@ -91,9 +193,36 @@ where } } +impl WithUid for Pair +where + Relation: Component, + Target: Component, +{ + fn uid() -> Uid + { + Uid::new_pair(&UidPairParams { + relation: Relation::uid(), + target: Target::uid(), + }) + } +} + +impl WithUid for Pair +where + Relation: Component, +{ + fn uid() -> Uid + { + Uid::new_pair(&UidPairParams { + relation: Relation::uid(), + target: Wildcard::uid(), + }) + } +} + impl QueryTermWithField for &'static [Pair] where - Relation: WithUid, + Relation: Component, { type Field<'a> = HandleIter<'a>; @@ -117,13 +246,13 @@ where } } -pub struct Handle<'world> +pub struct EntityTargetHandle<'world> { world: &'world World, pair_uid: Uid, } -impl Handle<'_> +impl EntityTargetHandle<'_> { #[must_use] pub fn get_target_entity(&self) -> Option> @@ -152,38 +281,19 @@ pub struct HandleIter<'a> impl<'a> Iterator for HandleIter<'a> { - type Item = Handle<'a>; + type Item = EntityTargetHandle<'a>; fn next(&mut self) -> Option { let matching_comp = self.inner.next()?; - Some(Handle { + Some(EntityTargetHandle { world: self.world, pair_uid: matching_comp.id(), }) } } -pub trait Element: sealed::Sealed -{ - type Value; -} - -impl Element for Uid -{ - type Value = Uid; -} - -impl sealed::Sealed for Uid {} - -impl Element for WithUidT -{ - type Value = Infallible; -} - -impl sealed::Sealed for WithUidT {} - /// Relation denoting a dependency to another entity #[derive(Debug, Default, Clone, Copy, Component)] pub struct DependsOn; @@ -192,7 +302,13 @@ pub struct DependsOn; #[derive(Debug, Default, Clone, Copy, Component)] pub struct ChildOf; -mod sealed +#[derive(Debug)] +pub struct Wildcard(Infallible); + +impl Wildcard { - pub trait Sealed {} + pub fn uid() -> Uid + { + Uid::wildcard() + } } diff --git a/ecs/src/uid.rs b/ecs/src/uid.rs index feed62c..a361882 100644 --- a/ecs/src/uid.rs +++ b/ecs/src/uid.rs @@ -218,7 +218,7 @@ pub struct PairParams pub target: Uid, } -pub trait With: 'static +pub trait With { fn uid() -> Uid; } @@ -230,14 +230,3 @@ impl With for ComponentT Self::id() } } - -#[derive(Debug)] -pub enum Wildcard {} - -impl With for Wildcard -{ - fn uid() -> Uid - { - Uid::wildcard() - } -} -- cgit v1.2.3-18-g5258