From 94e5e592baea2935af7c94ad44805a09d0e30740 Mon Sep 17 00:00:00 2001
From: HampusM <hampus@hampusmat.com>
Date: Wed, 9 Apr 2025 20:50:14 +0200
Subject: feat(ecs): replace Relationship component with pair UID support

---
 ecs/examples/event_loop.rs             |  17 +-
 ecs/examples/relationship.rs           |  16 +-
 ecs/examples/with_sole.rs              |   4 +-
 ecs/src/component/storage.rs           |  63 +++--
 ecs/src/component/storage/archetype.rs | 110 ++++++++-
 ecs/src/component/storage/graph.rs     |  19 +-
 ecs/src/entity.rs                      |  94 ++++++-
 ecs/src/lib.rs                         |  80 +++---
 ecs/src/pair.rs                        | 201 +++++++++++++++
 ecs/src/phase.rs                       |   6 +-
 ecs/src/query.rs                       |  70 ++++--
 ecs/src/query/flexible.rs              |   6 +-
 ecs/src/query/term.rs                  |  31 ++-
 ecs/src/relationship.rs                | 434 ---------------------------------
 ecs/src/uid.rs                         | 168 ++++++++++++-
 ecs/tests/query.rs                     |  14 +-
 16 files changed, 766 insertions(+), 567 deletions(-)
 create mode 100644 ecs/src/pair.rs
 delete mode 100644 ecs/src/relationship.rs

diff --git a/ecs/examples/event_loop.rs b/ecs/examples/event_loop.rs
index 2365eb0..61d7ba4 100644
--- a/ecs/examples/event_loop.rs
+++ b/ecs/examples/event_loop.rs
@@ -1,6 +1,6 @@
 use ecs::actions::Actions;
+use ecs::pair::{ChildOf, Pair};
 use ecs::phase::{Phase, UPDATE as UPDATE_PHASE};
-use ecs::relationship::{ChildOf, Relationship};
 use ecs::{static_entity, Component, Query, World};
 
 #[derive(Component)]
@@ -65,20 +65,11 @@ fn age(query: Query<(&mut Health, &Name)>, mut actions: Actions)
     }
 }
 
-static_entity!(
-    SHEER_PHASE,
-    (Phase, <Relationship<ChildOf, Phase>>::new(*UPDATE_PHASE))
-);
+static_entity!(SHEER_PHASE, (Phase, Pair::new::<ChildOf>(*UPDATE_PHASE)));
 
-static_entity!(
-    FEED_PHASE,
-    (Phase, <Relationship<ChildOf, Phase>>::new(*SHEER_PHASE))
-);
+static_entity!(FEED_PHASE, (Phase, Pair::new::<ChildOf>(*SHEER_PHASE)));
 
-static_entity!(
-    AGE_PHASE,
-    (Phase, <Relationship<ChildOf, Phase>>::new(*FEED_PHASE))
-);
+static_entity!(AGE_PHASE, (Phase, Pair::new::<ChildOf>(*FEED_PHASE)));
 
 fn main()
 {
diff --git a/ecs/examples/relationship.rs b/ecs/examples/relationship.rs
index 240884a..b607398 100644
--- a/ecs/examples/relationship.rs
+++ b/ecs/examples/relationship.rs
@@ -1,5 +1,6 @@
+use ecs::pair::Pair;
 use ecs::phase::START as START_PHASE;
-use ecs::relationship::Relationship;
+use ecs::uid::Wildcard;
 use ecs::{Component, Query, World};
 
 #[derive(Component)]
@@ -17,16 +18,19 @@ struct Health
     health: u32,
 }
 
+#[derive(Component)]
 struct Holding;
 
-fn print_player_stats(
-    player_query: Query<(&Player, &Health, &Relationship<Holding, Sword>)>,
-)
+fn print_player_stats(player_query: Query<(&Player, &Health, Pair<Holding, Wildcard>)>)
 {
     for (_, health, sword_relationship) in &player_query {
         println!("Player health: {}", health.health);
 
-        if let Some(sword) = sword_relationship.get(0) {
+        if let Some(sword_ent) = sword_relationship.get_target_entity() {
+            let sword = sword_ent
+                .get::<Sword>()
+                .expect("Sword entity is missing sword component");
+
             println!("Player sword attack strength: {}", sword.attack_strength);
         }
     }
@@ -43,7 +47,7 @@ fn main()
     world.create_entity((
         Player,
         Health { health: 180 },
-        Relationship::<Holding, Sword>::new(sword_uid),
+        Pair::new::<Holding>(sword_uid),
     ));
 
     world.step();
diff --git a/ecs/examples/with_sole.rs b/ecs/examples/with_sole.rs
index 689e562..c3feaab 100644
--- a/ecs/examples/with_sole.rs
+++ b/ecs/examples/with_sole.rs
@@ -1,5 +1,5 @@
+use ecs::pair::{ChildOf, Pair};
 use ecs::phase::{Phase, UPDATE as UPDATE_PHASE};
-use ecs::relationship::{ChildOf, Relationship};
 use ecs::sole::Single;
 use ecs::{static_entity, Component, Query, Sole, World};
 
@@ -33,7 +33,7 @@ fn print_total_ammo_count(ammo_counter: Single<AmmoCounter>)
 
 static_entity!(
     PRINT_AMMO_COUNT_PHASE,
-    (Phase, <Relationship<ChildOf, Phase>>::new(*UPDATE_PHASE))
+    (Phase, Pair::new::<ChildOf>(*UPDATE_PHASE))
 );
 
 fn main()
diff --git a/ecs/src/component/storage.rs b/ecs/src/component/storage.rs
index 53f51f2..9cf4433 100644
--- a/ecs/src/component/storage.rs
+++ b/ecs/src/component/storage.rs
@@ -33,14 +33,43 @@ pub struct ArchetypeSearchTerms<'a>
 
 impl ArchetypeSearchTerms<'_>
 {
-    fn excluded_contains(&self, uid: Uid) -> bool
+    fn excluded_contains(&self, comp_id: Uid) -> bool
     {
-        self.excluded_components.binary_search(&uid).is_ok()
+        let comp_id_kind = comp_id.kind();
+
+        debug_assert!(
+            comp_id_kind == UidKind::Component
+                || (comp_id_kind == UidKind::Pair
+                    && comp_id.target_component() != Uid::wildcard())
+        );
+
+        let is_found = self.excluded_components.binary_search(&comp_id).is_ok();
+
+        if !is_found && comp_id_kind == UidKind::Pair {
+            return self.excluded_components.iter().any(|excluded_comp_id| {
+                excluded_comp_id.kind() == UidKind::Pair
+                    && excluded_comp_id.has_same_relation_as(comp_id)
+                    && excluded_comp_id.target_component() == Uid::wildcard()
+            });
+        }
+
+        is_found
     }
 
-    fn required_contains(&self, uid: Uid) -> bool
+    fn contains_conflicting(&self) -> bool
     {
-        self.required_components.binary_search(&uid).is_ok()
+        self.excluded_components.iter().any(|excluded_comp_id| {
+            self.required_components
+                .binary_search(excluded_comp_id)
+                .is_ok()
+        })
+    }
+
+    fn archetype_contains_all_required(&self, archetype: &Archetype) -> bool
+    {
+        self.required_components
+            .iter()
+            .all(|comp_id| archetype.contains_matching_component(*comp_id))
     }
 }
 
@@ -61,11 +90,7 @@ impl Storage
     {
         let archetype_id = ArchetypeId::new(&search_terms.required_components);
 
-        if search_terms
-            .excluded_components
-            .iter()
-            .any(|excluded_comp_id| search_terms.required_contains(*excluded_comp_id))
-        {
+        if search_terms.contains_conflicting() {
             return ArchetypeRefIter {
                 storage: self,
                 pre_iter: Either::B(Vec::new().into_iter()),
@@ -81,7 +106,15 @@ impl Storage
                 .borrow_mut()
                 .push(ImaginaryArchetype {
                     id: archetype_id,
-                    component_ids: search_terms.required_components.to_vec(),
+                    component_ids: search_terms
+                        .required_components
+                        .iter()
+                        .copied()
+                        .filter(|required_comp_id| {
+                            required_comp_id.kind() != UidKind::Pair
+                                || required_comp_id.target_component() != Uid::wildcard()
+                        })
+                        .collect(),
                 });
 
             let found_archetypes = self.find_all_archetype_with_comps(&search_terms);
@@ -175,7 +208,7 @@ impl Storage
 
         if archetype_node
             .archetype()
-            .has_component_with_id(component_id)
+            .contains_component_with_exact_id(component_id)
         {
             return Err(Error::ComponentAlreadyInEntity {
                 entity: entity_uid,
@@ -266,7 +299,7 @@ impl Storage
 
         if !archetype_node
             .archetype()
-            .has_component_with_id(component_id)
+            .contains_component_with_exact_id(component_id)
         {
             return Err(Error::ComponentNotFoundInEntity {
                 entity: entity_uid,
@@ -367,11 +400,7 @@ impl Storage
                 continue;
             }
 
-            if !search_terms
-                .required_components
-                .iter()
-                .all(|comp_id| node.archetype().has_component_with_id(*comp_id))
-            {
+            if !search_terms.archetype_contains_all_required(node.archetype()) {
                 continue;
             }
 
diff --git a/ecs/src/component/storage/archetype.rs b/ecs/src/component/storage/archetype.rs
index b8ac826..10a665e 100644
--- a/ecs/src/component/storage/archetype.rs
+++ b/ecs/src/component/storage/archetype.rs
@@ -1,5 +1,8 @@
 use std::any::Any;
+use std::array::IntoIter as ArrayIntoIter;
 use std::hash::{DefaultHasher, Hash, Hasher};
+use std::iter::{Enumerate, Filter, Map, RepeatN, Zip};
+use std::option::IntoIter as OptionIntoIter;
 use std::slice::Iter as SliceIter;
 
 use hashbrown::HashMap;
@@ -7,7 +10,7 @@ use hashbrown::HashMap;
 use crate::component::Metadata as ComponentMetadata;
 use crate::lock::Lock;
 use crate::uid::{Kind as UidKind, Uid};
-use crate::util::HashMapExt;
+use crate::util::{Either, HashMapExt};
 
 #[derive(Debug)]
 pub struct Archetype
@@ -117,8 +120,54 @@ impl Archetype
         self.component_index_lookup.len()
     }
 
+    pub fn get_matching_component_indices(
+        &self,
+        component_id: Uid,
+    ) -> MatchingComponentIter
+    {
+        assert!(
+            component_id.kind() == UidKind::Component
+                || component_id.kind() == UidKind::Pair
+        );
+
+        if component_id.kind() == UidKind::Pair
+            && component_id.target_component() == Uid::wildcard()
+        {
+            return MatchingComponentIter {
+                inner: Either::A(
+                    self.component_ids
+                        .iter()
+                        .enumerate()
+                        .zip(std::iter::repeat_n(component_id, self.component_ids.len()))
+                        .filter(
+                            (|((_, other_comp_id), component_id)| {
+                                other_comp_id.kind() == UidKind::Pair
+                                    && other_comp_id.has_same_relation_as(*component_id)
+                            })
+                                as MatchingComponentIterFilterFn,
+                        )
+                        .map(|((index, other_comp_id), _)| (*other_comp_id, index)),
+                ),
+            };
+        }
+
+        MatchingComponentIter {
+            inner: Either::B(
+                [component_id]
+                    .into_iter()
+                    .zip(self.get_index_for_component(component_id).into_iter()),
+            ),
+        }
+    }
+
     pub fn get_index_for_component(&self, component_id: Uid) -> Option<usize>
     {
+        assert!(
+            component_id.kind() == UidKind::Component
+                || (component_id.kind() == UidKind::Pair
+                    && component_id.target_component() != Uid::wildcard())
+        );
+
         self.component_index_lookup.get(&component_id).copied()
     }
 
@@ -132,14 +181,69 @@ impl Archetype
         self.component_ids.iter().copied()
     }
 
-    pub fn has_component_with_id(&self, component_id: Uid) -> bool
+    pub fn contains_matching_component(&self, component_id: Uid) -> bool
+    {
+        let component_id_kind = component_id.kind();
+
+        debug_assert!(
+            component_id_kind == UidKind::Component || component_id_kind == UidKind::Pair
+        );
+
+        if component_id.kind() == UidKind::Pair
+            && component_id.target_component() == Uid::wildcard()
+        {
+            return self.component_ids.iter().any(|other_comp_id| {
+                other_comp_id.kind() == UidKind::Pair
+                    && other_comp_id.has_same_relation_as(component_id)
+            });
+        }
+
+        self.contains_component_with_exact_id(component_id)
+    }
+
+    pub fn contains_component_with_exact_id(&self, component_id: Uid) -> bool
     {
-        debug_assert_eq!(component_id.kind(), UidKind::Component);
+        let component_id_kind = component_id.kind();
+
+        debug_assert!(
+            component_id_kind == UidKind::Component
+                || (component_id_kind == UidKind::Pair
+                    && component_id.target_component() != Uid::wildcard())
+        );
 
         self.component_index_lookup.contains_key(&component_id)
     }
 }
 
+type MatchingComponentIterFilterFn = fn(&((usize, &Uid), Uid)) -> bool;
+
+type MatchingComponentIterMapFn = fn(((usize, &Uid), Uid)) -> (Uid, usize);
+
+#[derive(Debug)]
+pub struct MatchingComponentIter<'archetype>
+{
+    inner: Either<
+        Map<
+            Filter<
+                Zip<Enumerate<SliceIter<'archetype, Uid>>, RepeatN<Uid>>,
+                MatchingComponentIterFilterFn,
+            >,
+            MatchingComponentIterMapFn,
+        >,
+        Zip<ArrayIntoIter<Uid, 1>, OptionIntoIter<usize>>,
+    >,
+}
+
+impl Iterator for MatchingComponentIter<'_>
+{
+    type Item = (Uid, usize);
+
+    fn next(&mut self) -> Option<Self::Item>
+    {
+        self.inner.next()
+    }
+}
+
 #[derive(Debug)]
 pub struct EntityIter<'archetype>
 {
diff --git a/ecs/src/component/storage/graph.rs b/ecs/src/component/storage/graph.rs
index d38223a..29fa937 100644
--- a/ecs/src/component/storage/graph.rs
+++ b/ecs/src/component/storage/graph.rs
@@ -147,7 +147,11 @@ impl Graph
         let uniq_comp_id = target_node
             .archetype()
             .component_ids_sorted()
-            .find(|id| !subset_node.archetype().has_component_with_id(*id))
+            .find(|id| {
+                !subset_node
+                    .archetype()
+                    .contains_component_with_exact_id(*id)
+            })
             .unwrap();
 
         subset_node
@@ -177,7 +181,7 @@ impl Graph
                 .find(|other_archetype_comp_id| {
                     !target_node
                         .archetype()
-                        .has_component_with_id(*other_archetype_comp_id)
+                        .contains_component_with_exact_id(*other_archetype_comp_id)
                 })
                 .or_else(|| {
                     if target_node.archetype().component_cnt() != 0 {
@@ -204,7 +208,11 @@ impl Graph
         let extra_comp_id = superset_node
             .archetype()
             .component_ids_unsorted()
-            .find(|comp_id| !target_node.archetype().has_component_with_id(*comp_id))
+            .find(|comp_id| {
+                !target_node
+                    .archetype()
+                    .contains_component_with_exact_id(*comp_id)
+            })
             .expect("Archetype should contain one extra component ID");
 
         superset_node
@@ -242,7 +250,10 @@ impl ArchetypeNode
         insert_fn: impl FnOnce() -> ArchetypeEdges,
     ) -> &mut ArchetypeEdges
     {
-        debug_assert_eq!(component_id.kind(), UidKind::Component);
+        debug_assert!(matches!(
+            component_id.kind(),
+            UidKind::Component | UidKind::Pair
+        ));
 
         self.edges.entry(component_id).or_insert_with(insert_fn)
     }
diff --git a/ecs/src/entity.rs b/ecs/src/entity.rs
index a43f9ce..3f5a3d3 100644
--- a/ecs/src/entity.rs
+++ b/ecs/src/entity.rs
@@ -1,12 +1,26 @@
+use std::any::type_name;
+
 use linkme::distributed_slice;
 
-use crate::component::storage::archetype::{Archetype, Entity as ArchetypeEntity};
-use crate::uid::Uid;
+use crate::component::storage::archetype::{
+    Archetype,
+    Entity as ArchetypeEntity,
+    MatchingComponentIter as ArchetypeMatchingComponentIter,
+};
+use crate::component::{
+    Component,
+    Handle as ComponentHandle,
+    HandleFromEntityComponentRef,
+    HandleMut as ComponentHandleMut,
+};
+use crate::uid::{Kind as UidKind, Uid};
 use crate::{EntityComponentRef, World};
 
 /// A handle to a entity.
+#[derive(Debug)]
 pub struct Handle<'a>
 {
+    world: &'a World,
     archetype: &'a Archetype,
     entity: &'a ArchetypeEntity,
 }
@@ -21,20 +35,82 @@ impl<'a> Handle<'a>
         self.entity.uid()
     }
 
+    pub fn get<ComponentT: Component>(&self) -> Option<ComponentHandle<'_, ComponentT>>
+    {
+        assert_eq!(ComponentT::id().kind(), UidKind::Component);
+
+        let component = self.get_matching_components(ComponentT::id()).next()?;
+
+        Some(
+            ComponentHandle::from_entity_component_ref(Some(component), self.world)
+                .unwrap_or_else(|err| {
+                    panic!(
+                        "Taking component {} lock failed: {err}",
+                        type_name::<ComponentT>()
+                    );
+                }),
+        )
+    }
+
+    pub fn get_mut<ComponentT: Component>(
+        &self,
+    ) -> Option<ComponentHandleMut<'_, ComponentT>>
+    {
+        assert_eq!(ComponentT::id().kind(), UidKind::Component);
+
+        let component = self.get_matching_components(ComponentT::id()).next()?;
+
+        Some(
+            ComponentHandleMut::from_entity_component_ref(Some(component), self.world)
+                .unwrap_or_else(|err| {
+                    panic!(
+                        "Taking component {} lock failed: {err}",
+                        type_name::<ComponentT>()
+                    );
+                }),
+        )
+    }
+
     #[inline]
     #[must_use]
-    pub fn get_component(&self, component_uid: Uid) -> Option<EntityComponentRef<'a>>
+    pub fn get_matching_components(&self, component_uid: Uid)
+        -> MatchingComponentIter<'a>
     {
-        let index = self.archetype.get_index_for_component(component_uid)?;
+        MatchingComponentIter {
+            inner: self.archetype.get_matching_component_indices(component_uid),
+            entity: self.entity,
+        }
+    }
 
-        Some(EntityComponentRef::new(
-            self.entity.components().get(index).unwrap(),
-        ))
+    pub(crate) fn new(
+        world: &'a World,
+        archetype: &'a Archetype,
+        entity: &'a ArchetypeEntity,
+    ) -> Self
+    {
+        Self { world, archetype, entity }
     }
+}
+
+#[derive(Debug)]
+pub struct MatchingComponentIter<'a>
+{
+    inner: ArchetypeMatchingComponentIter<'a>,
+    entity: &'a ArchetypeEntity,
+}
+
+impl<'a> Iterator for MatchingComponentIter<'a>
+{
+    type Item = EntityComponentRef<'a>;
 
-    pub(crate) fn new(archetype: &'a Archetype, entity: &'a ArchetypeEntity) -> Self
+    fn next(&mut self) -> Option<Self::Item>
     {
-        Self { archetype, entity }
+        let (matching_component_id, index) = self.inner.next()?;
+
+        Some(EntityComponentRef::new(
+            matching_component_id,
+            self.entity.components().get(index).unwrap(),
+        ))
     }
 }
 
diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs
index 962c542..3b89dac 100644
--- a/ecs/src/lib.rs
+++ b/ecs/src/lib.rs
@@ -20,6 +20,7 @@ use crate::component::{
 use crate::entity::CREATE_STATIC_ENTITIES;
 use crate::extension::{Collector as ExtensionCollector, Extension};
 use crate::lock::{Lock, WriteGuard};
+use crate::pair::{ChildOf, DependsOn, Pair};
 use crate::phase::{Phase, START as START_PHASE};
 use crate::query::flexible::Query as FlexibleQuery;
 use crate::query::term::Without;
@@ -30,21 +31,19 @@ use crate::query::{
     Terms as QueryTerms,
     TermsBuilderInterface,
 };
-use crate::relationship::{ChildOf, DependsOn, Relationship};
 use crate::sole::Sole;
 use crate::stats::Stats;
 use crate::system::{System, SystemComponent};
-use crate::uid::{Kind as UidKind, Uid};
+use crate::uid::{Kind as UidKind, Uid, Wildcard};
 
 pub mod actions;
 pub mod component;
 pub mod entity;
 pub mod event;
 pub mod extension;
-mod lock;
+pub mod pair;
 pub mod phase;
 pub mod query;
-pub mod relationship;
 pub mod sole;
 pub mod stats;
 pub mod system;
@@ -55,6 +54,8 @@ pub mod util;
 #[doc(hidden)]
 pub mod private;
 
+mod lock;
+
 pub use ecs_macros::{Component, Sole};
 
 pub use crate::query::Query;
@@ -151,7 +152,7 @@ impl World
     {
         self.create_entity((
             SystemComponent { system: system.into_type_erased() },
-            Relationship::<DependsOn, Phase>::new(phase_euid),
+            Pair::new::<DependsOn>(phase_euid),
         ));
     }
 
@@ -278,7 +279,7 @@ impl World
                         archetype
                             .component_ids_sorted()
                             .into_iter()
-                            .map(|comp_id| comp_id.id().to_string())
+                            .map(|comp_id| comp_id.to_string())
                             .collect::<Vec<_>>()
                             .join(", ")
                     ))
@@ -305,16 +306,18 @@ impl World
 
     fn query_and_run_systems(&self, phase_euid: Uid)
     {
-        let system_comps_query =
-            self.query::<(&SystemComponent, &Relationship<DependsOn, Phase>), ()>();
-
-        let system_iter = system_comps_query.iter().filter(|(_, phase_rel)| {
-            phase_rel
-                .target_uids()
-                .any(|target_uid| target_uid == phase_euid)
-        });
+        let system_query = self.flexible_query(
+            QueryTerms::<2>::builder()
+                .with_required([
+                    SystemComponent::id(),
+                    Pair::new::<DependsOn>(phase_euid).id(),
+                ])
+                .build(),
+        );
 
-        for (system_component, _) in system_iter {
+        for (system_component,) in
+            QueryIter::<(&SystemComponent,), _>::new(self, system_query.iter())
+        {
             // SAFETY: The world lives long enough
             unsafe {
                 system_component.system.run(self);
@@ -324,33 +327,32 @@ impl World
 
     fn perform_child_phases(&self, parent_phase_euid: Uid)
     {
-        let phase_query = self.query::<(&Phase, &Relationship<ChildOf, Phase>), ()>();
-
-        for (child_phase_euid, (_, phase_rel)) in phase_query.iter_with_euids() {
-            if !phase_rel
-                .target_uids()
-                .any(|phase_euid| phase_euid == parent_phase_euid)
-            {
-                continue;
-            }
+        let phase_query = self.flexible_query(
+            QueryTerms::<2>::builder()
+                .with_required([
+                    Phase::id(),
+                    Pair::new::<ChildOf>(parent_phase_euid).id(),
+                ])
+                .build(),
+        );
 
-            self.query_and_run_systems(child_phase_euid);
-            self.perform_child_phases(child_phase_euid);
+        for child_phase_entity in phase_query.iter() {
+            self.query_and_run_systems(child_phase_entity.uid());
+            self.perform_child_phases(child_phase_entity.uid());
         }
     }
 
     fn perform_phases(&self)
     {
-        let phase_query =
-            self.query::<(&Phase,), (Without<Relationship<ChildOf, Phase>>,)>();
+        let phase_query = self.query::<(&Phase,), (Without<Pair<ChildOf, Wildcard>>,)>();
 
-        for (phase_euid, (_,)) in phase_query.iter_with_euids() {
-            if phase_euid == *START_PHASE {
+        for (phase_entity_id, _) in phase_query.iter_with_euids() {
+            if phase_entity_id == *START_PHASE {
                 continue;
             }
 
-            self.query_and_run_systems(phase_euid);
-            self.perform_child_phases(phase_euid);
+            self.query_and_run_systems(phase_entity_id);
+            self.perform_child_phases(phase_entity_id);
         }
     }
 
@@ -604,19 +606,25 @@ impl Default for WorldData
 #[derive(Debug)]
 pub struct EntityComponentRef<'a>
 {
-    comp: &'a ArchetypeEntityComponent,
+    component_id: Uid,
+    component: &'a ArchetypeEntityComponent,
 }
 
 impl<'a> EntityComponentRef<'a>
 {
     fn component(&self) -> &'a Lock<Box<dyn Any>>
     {
-        self.comp.component()
+        self.component.component()
+    }
+
+    pub fn id(&self) -> Uid
+    {
+        self.component_id
     }
 
-    fn new(comp: &'a ArchetypeEntityComponent) -> Self
+    fn new(component_id: Uid, comp: &'a ArchetypeEntityComponent) -> Self
     {
-        Self { comp }
+        Self { component_id, component: comp }
     }
 }
 
diff --git a/ecs/src/pair.rs b/ecs/src/pair.rs
new file mode 100644
index 0000000..27e535e
--- /dev/null
+++ b/ecs/src/pair.rs
@@ -0,0 +1,201 @@
+use std::convert::Infallible;
+
+use crate::component::storage::Storage as ComponentStorage;
+use crate::component::{IntoParts as IntoComponentParts, Parts as ComponentParts};
+use crate::entity::{
+    Handle as EntityHandle,
+    MatchingComponentIter as EntityMatchingComponentIter,
+};
+use crate::lock::ReadGuard;
+use crate::query::{
+    TermWithField as QueryTermWithField,
+    TermsBuilder as QueryTermsBuilder,
+    TermsBuilderInterface,
+};
+use crate::uid::{PairParams as UidPairParams, Uid, Wildcard, With as WithUid};
+use crate::{Component, World};
+
+#[derive(Debug)]
+pub struct Pair<RelationElem: Element, TargetElem: Element>
+{
+    relation: RelationElem,
+    target: TargetElem,
+}
+
+impl Pair<Uid, Uid>
+{
+    pub fn new<Relation: WithUid>(target: Uid) -> Self
+    {
+        Self { relation: Relation::uid(), target }
+    }
+
+    pub fn id(&self) -> Uid
+    {
+        Uid::new_pair(UidPairParams {
+            relation: self.relation,
+            target: self.target,
+        })
+    }
+}
+
+impl IntoComponentParts for Pair<Uid, Uid>
+{
+    fn into_parts(self) -> ComponentParts
+    {
+        ComponentParts::builder().name("Pair").build(self.id(), ())
+    }
+}
+
+impl<Relation, Target> QueryTermWithField for Pair<Relation, Target>
+where
+    Relation: WithUid,
+    Target: WithUid,
+{
+    type Field<'a> = Handle<'a>;
+
+    fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+        terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>,
+    )
+    {
+        terms_builder.with_required([Self::uid()]);
+    }
+
+    fn get_field<'world>(
+        entity_handle: &EntityHandle<'world>,
+        world: &'world World,
+    ) -> Self::Field<'world>
+    {
+        let first_matching_comp = entity_handle
+            .get_matching_components(Self::uid())
+            .next()
+            .expect("Not possible");
+
+        Handle {
+            world,
+            component_storage_lock: world.data.component_storage.read_nonblock().unwrap(),
+            pair_uid: first_matching_comp.id(),
+        }
+    }
+}
+
+impl<Relation, Target> WithUid for Pair<Relation, Target>
+where
+    Relation: WithUid,
+    Target: WithUid,
+{
+    fn uid() -> Uid
+    {
+        Uid::new_pair(UidPairParams {
+            relation: Relation::uid(),
+            target: Target::uid(),
+        })
+    }
+}
+
+impl<Relation> QueryTermWithField for &'static [Pair<Relation, Wildcard>]
+where
+    Relation: WithUid,
+{
+    type Field<'a> = HandleIter<'a>;
+
+    fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+        terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>,
+    )
+    {
+        terms_builder.with_required([Pair::<Relation, Wildcard>::uid()]);
+    }
+
+    fn get_field<'world>(
+        entity_handle: &EntityHandle<'world>,
+        world: &'world World,
+    ) -> Self::Field<'world>
+    {
+        HandleIter {
+            inner: entity_handle
+                .get_matching_components(Pair::<Relation, Wildcard>::uid()),
+            world,
+        }
+    }
+}
+
+pub struct Handle<'world>
+{
+    world: &'world World,
+    component_storage_lock: ReadGuard<'world, ComponentStorage>,
+    pair_uid: Uid,
+}
+
+impl Handle<'_>
+{
+    pub fn get_target_entity(&self) -> Option<EntityHandle<'_>>
+    {
+        let archetype = self
+            .component_storage_lock
+            .get_entity_archetype(self.pair_uid.target_entity())?;
+
+        let archetype_entity = archetype
+            .get_entity_by_id(self.pair_uid.target_entity())
+            .expect("Not possible");
+
+        Some(EntityHandle::new(self.world, archetype, archetype_entity))
+    }
+}
+
+pub struct HandleIter<'a>
+{
+    inner: EntityMatchingComponentIter<'a>,
+    world: &'a World,
+}
+
+impl<'a> Iterator for HandleIter<'a>
+{
+    type Item = Handle<'a>;
+
+    fn next(&mut self) -> Option<Self::Item>
+    {
+        let matching_comp = self.inner.next()?;
+
+        Some(Handle {
+            world: self.world,
+            component_storage_lock: self
+                .world
+                .data
+                .component_storage
+                .read_nonblock()
+                .unwrap(),
+            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<WithUidT: WithUid> Element for WithUidT
+{
+    type Value = Infallible;
+}
+
+impl<WithUidT: WithUid> sealed::Sealed for WithUidT {}
+
+/// Relation denoting a dependency to another entity
+#[derive(Debug, Default, Clone, Copy, Component)]
+pub struct DependsOn;
+
+/// Relation denoting being the child of another entity.
+#[derive(Debug, Default, Clone, Copy, Component)]
+pub struct ChildOf;
+
+mod sealed
+{
+    pub trait Sealed {}
+}
diff --git a/ecs/src/phase.rs b/ecs/src/phase.rs
index b8660f2..48dd38d 100644
--- a/ecs/src/phase.rs
+++ b/ecs/src/phase.rs
@@ -1,6 +1,6 @@
 use ecs_macros::Component;
 
-use crate::relationship::{ChildOf, Relationship};
+use crate::pair::{ChildOf, Pair};
 use crate::static_entity;
 
 #[derive(Debug, Default, Clone, Copy, Component)]
@@ -10,6 +10,6 @@ static_entity!(pub START, (Phase,));
 
 static_entity!(pub PRE_UPDATE, (Phase,));
 
-static_entity!(pub UPDATE, (Phase, <Relationship<ChildOf, Phase>>::new(*PRE_UPDATE)));
+static_entity!(pub UPDATE, (Phase, Pair::new::<ChildOf>(*PRE_UPDATE)));
 
-static_entity!(pub PRESENT, (Phase, <Relationship<ChildOf, Phase>>::new(*UPDATE)));
+static_entity!(pub PRESENT, (Phase, Pair::new::<ChildOf>(*UPDATE)));
diff --git a/ecs/src/query.rs b/ecs/src/query.rs
index 7542f0d..1d27b7a 100644
--- a/ecs/src/query.rs
+++ b/ecs/src/query.rs
@@ -7,7 +7,7 @@ use crate::component::{Component, HandleFromEntityComponentRef, Ref as Component
 use crate::entity::Handle as EntityHandle;
 use crate::query::flexible::{Iter as FlexibleQueryIter, Query as FlexibleQuery};
 use crate::system::{Param as SystemParam, System};
-use crate::uid::Uid;
+use crate::uid::{Kind as UidKind, Uid, With as WithUid};
 use crate::util::array_vec::ArrayVec;
 use crate::util::Array;
 use crate::World;
@@ -111,7 +111,7 @@ where
 impl<'query, 'world, FieldTerms, FieldlessTerms> IntoIterator
     for &'query Query<'world, FieldTerms, FieldlessTerms>
 where
-    FieldTerms: TermWithFieldTuple + 'world,
+    FieldTerms: TermWithFieldTuple,
     FieldlessTerms: TermWithoutFieldTuple,
 {
     type IntoIter = Iter<'query, 'world, FieldTerms, FlexibleQueryIter<'query>>;
@@ -171,11 +171,13 @@ pub struct TermsBuilder<const MAX_TERM_CNT: usize>
 
 pub trait TermsBuilderInterface
 {
-    fn with<ComponentT: Component>(self) -> Self;
+    fn with<WithUidT: WithUid>(self) -> Self;
 
-    fn without<ComponentT: Component>(self) -> Self;
+    fn without<WithUidT: WithUid>(self) -> Self;
 
     fn with_required(self, ids: impl Array<Uid>) -> Self;
+
+    fn without_ids(self, ids: impl Array<Uid>) -> Self;
 }
 
 macro_rules! impl_terms_builder {
@@ -196,25 +198,25 @@ macro_rules! impl_terms_builder {
 
 impl_terms_builder! {
     #[allow(unused_mut)]
-    fn with<ComponentT: Component>(mut self) -> Self
+    fn with<WithUidT: WithUid>(mut self) -> Self
     {
         let insert_index = self.required_components
-            .partition_point(|id| *id <= ComponentT::id());
+            .partition_point(|id| *id <= WithUidT::uid());
 
         self.required_components
-            .insert(insert_index, ComponentT::id());
+            .insert(insert_index, WithUidT::uid());
 
         self
     }
 
     #[allow(unused_mut)]
-    fn without<ComponentT: Component>(mut self) -> Self
+    fn without<WithUidT: WithUid>(mut self) -> Self
     {
         let insert_index = self.excluded_components
-            .partition_point(|id| *id <= ComponentT::id());
+            .partition_point(|id| *id <= WithUidT::uid());
 
         self.excluded_components
-            .insert(insert_index, ComponentT::id());
+            .insert(insert_index, WithUidT::uid());
 
         self
     }
@@ -250,6 +252,38 @@ impl_terms_builder! {
 
         self
     }
+
+    #[allow(unused_mut)]
+    fn without_ids(mut self, mut ids: impl Array<Uid>) -> Self
+    {
+        if !ids.as_ref().is_sorted() {
+            ids.as_mut().sort();
+        }
+
+        if self.excluded_components.len() == 0 {
+            self.excluded_components.extend(ids);
+            return self;
+        }
+
+        let mut id_iter = ids.into_iter();
+
+        while let Some(id) = id_iter.next() {
+            let insert_index = self.excluded_components
+                .partition_point(|other_id| *other_id <= id);
+
+            if insert_index == self.excluded_components.len() {
+                self.excluded_components.extend([id].into_iter().chain(id_iter));
+
+                return self;
+            }
+
+            self.excluded_components
+                .insert(insert_index, id);
+
+        }
+
+        self
+    }
 }
 
 impl<const MAX_TERM_CNT: usize> TermsBuilder<MAX_TERM_CNT>
@@ -303,8 +337,12 @@ impl<ComponentRefT: ComponentRef> TermWithField for ComponentRefT
         world: &'world World,
     ) -> Self::Field<'world>
     {
+        assert_eq!(ComponentRefT::Component::id().kind(), UidKind::Component);
+
         Self::Field::from_entity_component_ref(
-            entity_handle.get_component(ComponentRefT::Component::id()),
+            entity_handle
+                .get_matching_components(ComponentRefT::Component::id())
+                .next(),
             world,
         )
         .unwrap_or_else(|err| {
@@ -339,7 +377,7 @@ pub trait TermWithFieldTuple
 
 pub struct Iter<'query, 'world, FieldTerms, EntityHandleIter>
 where
-    FieldTerms: TermWithFieldTuple + 'world,
+    FieldTerms: TermWithFieldTuple,
     EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
 {
     world: &'world World,
@@ -350,7 +388,7 @@ where
 impl<'query, 'world, FieldTerms, EntityHandleIter>
     Iter<'query, 'world, FieldTerms, EntityHandleIter>
 where
-    FieldTerms: TermWithFieldTuple + 'world,
+    FieldTerms: TermWithFieldTuple,
     EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
     'world: 'query,
 {
@@ -369,7 +407,7 @@ where
 impl<'query, 'world, FieldTerms, EntityHandleIter> Iterator
     for Iter<'query, 'world, FieldTerms, EntityHandleIter>
 where
-    FieldTerms: TermWithFieldTuple + 'world,
+    FieldTerms: TermWithFieldTuple,
     EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
     'world: 'query,
 {
@@ -385,7 +423,7 @@ where
 
 pub struct ComponentAndEuidIter<'query, 'world, FieldTerms, EntityHandleIter>
 where
-    FieldTerms: TermWithFieldTuple + 'world,
+    FieldTerms: TermWithFieldTuple,
     EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
 {
     world: &'world World,
@@ -396,7 +434,7 @@ where
 impl<'query, 'world, FieldTerms, EntityHandleIter> Iterator
     for ComponentAndEuidIter<'query, 'world, FieldTerms, EntityHandleIter>
 where
-    FieldTerms: TermWithFieldTuple + 'world,
+    FieldTerms: TermWithFieldTuple,
     EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
     'world: 'query,
 {
diff --git a/ecs/src/query/flexible.rs b/ecs/src/query/flexible.rs
index 2f0b5e7..3e72b34 100644
--- a/ecs/src/query/flexible.rs
+++ b/ecs/src/query/flexible.rs
@@ -16,6 +16,7 @@ use crate::World;
 #[derive(Debug)]
 pub struct Query<'world, const MAX_TERM_CNT: usize>
 {
+    world: &'world World,
     component_storage: ReadGuard<'world, ComponentStorage>,
     terms: Terms<MAX_TERM_CNT>,
 }
@@ -27,6 +28,7 @@ impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT>
     pub fn iter(&self) -> Iter<'_>
     {
         Iter {
+            world: self.world,
             iter: self
                 .component_storage
                 .search_archetypes(ArchetypeSearchTerms {
@@ -45,6 +47,7 @@ impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT>
     pub(crate) fn new(world: &'world World, terms: Terms<MAX_TERM_CNT>) -> Self
     {
         Self {
+            world,
             component_storage: world
                 .data
                 .component_storage
@@ -57,6 +60,7 @@ impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT>
 
 pub struct Iter<'query>
 {
+    world: &'query World,
     iter: QueryEntityIter<'query>,
 }
 
@@ -68,7 +72,7 @@ impl<'query> Iterator for Iter<'query>
     {
         let (archetype, entity) = self.iter.next()?;
 
-        Some(EntityHandle::new(archetype, entity))
+        Some(EntityHandle::new(self.world, archetype, entity))
     }
 }
 
diff --git a/ecs/src/query/term.rs b/ecs/src/query/term.rs
index 78668c5..597dd1a 100644
--- a/ecs/src/query/term.rs
+++ b/ecs/src/query/term.rs
@@ -8,42 +8,43 @@ use crate::query::{
     TermsBuilder,
     TermsBuilderInterface,
 };
+use crate::uid::With as WithUid;
 
-pub struct With<ComponentT>
+pub struct With<WithUidT>
 where
-    ComponentT: Component,
+    WithUidT: WithUid,
 {
-    _pd: PhantomData<ComponentT>,
+    _pd: PhantomData<WithUidT>,
 }
 
-impl<ComponentT> TermWithoutField for With<ComponentT>
+impl<WithUidT> TermWithoutField for With<WithUidT>
 where
-    ComponentT: Component,
+    WithUidT: WithUid,
 {
     fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
         terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
     )
     {
-        terms_builder.with::<ComponentT>();
+        terms_builder.with::<WithUidT>();
     }
 }
 
-pub struct Without<ComponentT>
+pub struct Without<WithUidT>
 where
-    ComponentT: Component,
+    WithUidT: WithUid,
 {
-    _pd: PhantomData<ComponentT>,
+    _pd: PhantomData<WithUidT>,
 }
 
-impl<ComponentT> TermWithoutField for Without<ComponentT>
+impl<WithUidT> TermWithoutField for Without<WithUidT>
 where
-    ComponentT: Component,
+    WithUidT: WithUid,
 {
     fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
         terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
     )
     {
-        terms_builder.without::<ComponentT>();
+        terms_builder.without::<WithUidT>();
     }
 }
 
@@ -66,7 +67,11 @@ where
     {
         Some(
             ComponentRefT::Handle::<'world>::from_entity_component_ref(
-                Some(entity_handle.get_component(ComponentRefT::Component::id())?),
+                Some(
+                    entity_handle
+                        .get_matching_components(ComponentRefT::Component::id())
+                        .next()?,
+                ),
                 world,
             )
             .unwrap_or_else(|err| {
diff --git a/ecs/src/relationship.rs b/ecs/src/relationship.rs
deleted file mode 100644
index e368dd4..0000000
--- a/ecs/src/relationship.rs
+++ /dev/null
@@ -1,434 +0,0 @@
-use std::any::type_name;
-use std::marker::PhantomData;
-
-use ecs_macros::Component;
-
-use crate::component::storage::Storage as ComponentStorage;
-use crate::component::{
-    Component,
-    Handle as ComponentHandle,
-    HandleError as ComponentHandleError,
-    HandleFromEntityComponentRef,
-    HandleMut as ComponentHandleMut,
-};
-use crate::lock::ReadGuard;
-use crate::uid::{Kind as UidKind, Uid};
-use crate::{EntityComponentRef, World};
-
-/// A relationship to one or more targets.
-#[derive(Debug, Component)]
-#[component(
-    handle_type = Relation<'component, Kind, ComponentT>,
-    handle_mut_type = RelationMut<'component, Kind, ComponentT>,
-)]
-pub struct Relationship<Kind, ComponentT: Component>
-where
-    Kind: 'static,
-{
-    entity_uid: SingleOrMultiple<Uid>,
-    _pd: PhantomData<(Kind, ComponentT)>,
-}
-
-impl<Kind, ComponentT> Relationship<Kind, ComponentT>
-where
-    ComponentT: Component,
-{
-    /// Creates a new `Relationship` with a single target.
-    #[must_use]
-    pub fn new(entity_uid: Uid) -> Self
-    {
-        debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
-
-        Self {
-            entity_uid: SingleOrMultiple::Single(entity_uid),
-            _pd: PhantomData,
-        }
-    }
-
-    /// Creates a new `Relationship` with multiple targets.
-    #[must_use]
-    pub fn new_multiple(entity_uids: impl IntoIterator<Item = Uid>) -> Self
-    {
-        let uids = entity_uids.into_iter().collect::<Vec<_>>();
-
-        for euid in &uids {
-            debug_assert_eq!(euid.kind(), UidKind::Entity);
-        }
-
-        Self {
-            entity_uid: SingleOrMultiple::Multiple(uids),
-            _pd: PhantomData,
-        }
-    }
-}
-
-pub struct RelationMut<'rel_comp, Kind, ComponentT>
-where
-    Kind: 'static,
-    ComponentT: Component,
-{
-    component_storage_lock: ReadGuard<'rel_comp, ComponentStorage>,
-    relationship_comp: ComponentHandleMut<'rel_comp, Relationship<Kind, ComponentT>>,
-}
-
-impl<'rel_comp, Kind, ComponentT> HandleFromEntityComponentRef<'rel_comp>
-    for RelationMut<'rel_comp, Kind, ComponentT>
-where
-    ComponentT: Component,
-{
-    type Error = ComponentHandleError;
-
-    fn from_entity_component_ref(
-        entity_component_ref: Option<EntityComponentRef<'rel_comp>>,
-        world: &'rel_comp World,
-    ) -> Result<Self, Self::Error>
-    {
-        let relationship_comp_handle_from_ent_comp_ref = ComponentHandleMut::<
-            Relationship<Kind, ComponentT>,
-        >::from_entity_component_ref;
-
-        let relationship_comp =
-            relationship_comp_handle_from_ent_comp_ref(entity_component_ref, world)?;
-
-        let component_storage_lock = world
-            .data
-            .component_storage
-            .read_nonblock()
-            .expect("Failed to aquire read-only component storage lock");
-
-        Ok(Self {
-            relationship_comp,
-            component_storage_lock,
-        })
-    }
-}
-
-impl<'rel_comp, Kind, ComponentT> RelationMut<'rel_comp, Kind, ComponentT>
-where
-    ComponentT: Component,
-{
-    /// Returns the component of the target at the specified index.
-    ///
-    /// # Panics
-    /// Will panic if the entity does not exist in the archetype it belongs to. This
-    /// should hopefully never happend.
-    #[must_use]
-    pub fn get(&self, index: usize) -> Option<ComponentHandleMut<'_, ComponentT>>
-    {
-        let target = self.get_target(index)?;
-
-        let archetype = self.component_storage_lock.get_entity_archetype(*target)?;
-
-        let entity = archetype
-            .get_entity_by_id(*target)
-            .expect("Target entity is gone from archetype");
-
-        let component_index = archetype.get_index_for_component(ComponentT::id())?;
-
-        let component = ComponentHandleMut::new(
-            entity
-                .components()
-                .get(component_index)?
-                .component()
-                .write_nonblock()
-                .unwrap_or_else(|_| {
-                    panic!(
-                        "Failed to aquire read-write lock of component {}",
-                        type_name::<ComponentT>()
-                    )
-                }),
-        );
-
-        Some(component)
-    }
-
-    /// Returns a reference to the target at the specified index.
-    #[must_use]
-    pub fn get_target(&self, index: usize) -> Option<&Uid>
-    {
-        match &self.relationship_comp.entity_uid {
-            SingleOrMultiple::Single(entity_uid) if index == 0 => Some(entity_uid),
-            SingleOrMultiple::Multiple(entity_uids) => entity_uids.get(index),
-            SingleOrMultiple::Single(_) => None,
-        }
-    }
-
-    /// Returns a mutable reference to the target at the specified index.
-    #[must_use]
-    pub fn get_target_mut(&mut self, index: usize) -> Option<&mut Uid>
-    {
-        match &mut self.relationship_comp.entity_uid {
-            SingleOrMultiple::Single(entity_uid) if index == 0 => Some(entity_uid),
-            SingleOrMultiple::Multiple(entity_uids) => entity_uids.get_mut(index),
-            SingleOrMultiple::Single(_) => None,
-        }
-    }
-
-    /// Adds a target to the relationship.
-    pub fn add_target(&mut self, entity_uid: Uid)
-    {
-        debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
-
-        match &mut self.relationship_comp.entity_uid {
-            SingleOrMultiple::Single(prev_entity_uid) => {
-                self.relationship_comp.entity_uid =
-                    SingleOrMultiple::Multiple(vec![*prev_entity_uid, entity_uid]);
-            }
-            SingleOrMultiple::Multiple(entity_uids) => entity_uids.push(entity_uid),
-        }
-    }
-
-    /// Removes a target to the relationship, returning it.
-    pub fn remove_target(&mut self, index: usize) -> Option<Uid>
-    {
-        match &mut self.relationship_comp.entity_uid {
-            SingleOrMultiple::Single(entity_uid) => {
-                let prev_entity_uid = *entity_uid;
-
-                self.relationship_comp.entity_uid =
-                    SingleOrMultiple::Multiple(Vec::new());
-
-                Some(prev_entity_uid)
-            }
-            SingleOrMultiple::Multiple(entity_uids) => {
-                if index >= entity_uids.len() {
-                    return None;
-                }
-
-                Some(entity_uids.remove(index))
-            }
-        }
-    }
-
-    #[must_use]
-    pub fn target_count(&self) -> usize
-    {
-        match &self.relationship_comp.entity_uid {
-            SingleOrMultiple::Single(_) => 1,
-            SingleOrMultiple::Multiple(entity_uids) => entity_uids.len(),
-        }
-    }
-
-    /// Returns a iterator of the components of the targets of this relationship.
-    #[must_use]
-    pub fn iter(&self) -> TargetComponentIterMut<'_, 'rel_comp, Kind, ComponentT>
-    {
-        TargetComponentIterMut { relation: self, index: 0 }
-    }
-}
-
-impl<'relationship, 'rel_comp, Kind, ComponentT> IntoIterator
-    for &'relationship RelationMut<'rel_comp, Kind, ComponentT>
-where
-    'relationship: 'rel_comp,
-    ComponentT: Component,
-{
-    type IntoIter = TargetComponentIterMut<'relationship, 'rel_comp, Kind, ComponentT>;
-    type Item = ComponentHandleMut<'rel_comp, ComponentT>;
-
-    fn into_iter(self) -> Self::IntoIter
-    {
-        self.iter()
-    }
-}
-
-/// Iterator of the components of the targets of a relationship.
-pub struct TargetComponentIterMut<'relationship, 'rel_comp, Kind, ComponentT>
-where
-    Kind: 'static,
-    ComponentT: Component,
-{
-    relation: &'relationship RelationMut<'rel_comp, Kind, ComponentT>,
-    index: usize,
-}
-
-impl<'relationship, 'rel_comp, Kind, ComponentT> Iterator
-    for TargetComponentIterMut<'relationship, 'rel_comp, Kind, ComponentT>
-where
-    'relationship: 'rel_comp,
-    Kind: 'static,
-    ComponentT: Component,
-{
-    type Item = ComponentHandleMut<'rel_comp, ComponentT>;
-
-    fn next(&mut self) -> Option<Self::Item>
-    {
-        let index = self.index;
-
-        self.index += 1;
-
-        self.relation.get(index)
-    }
-}
-
-#[derive(Debug)]
-enum SingleOrMultiple<Value>
-{
-    Single(Value),
-    Multiple(Vec<Value>),
-}
-
-pub struct Relation<'rel_comp, Kind, ComponentT>
-where
-    Kind: 'static,
-    ComponentT: Component,
-{
-    component_storage_lock: ReadGuard<'rel_comp, ComponentStorage>,
-    relationship_comp: ComponentHandle<'rel_comp, Relationship<Kind, ComponentT>>,
-}
-
-impl<'rel_comp, Kind, ComponentT> HandleFromEntityComponentRef<'rel_comp>
-    for Relation<'rel_comp, Kind, ComponentT>
-where
-    ComponentT: Component,
-{
-    type Error = ComponentHandleError;
-
-    fn from_entity_component_ref(
-        entity_component_ref: Option<EntityComponentRef<'rel_comp>>,
-        world: &'rel_comp World,
-    ) -> Result<Self, Self::Error>
-    {
-        let relationship_comp_handle_from_ent_comp_ref =
-            ComponentHandle::<Relationship<Kind, ComponentT>>::from_entity_component_ref;
-
-        let relationship_comp =
-            relationship_comp_handle_from_ent_comp_ref(entity_component_ref, world)?;
-
-        let component_storage_lock = world
-            .data
-            .component_storage
-            .read_nonblock()
-            .expect("Failed to aquire read-only component storage lock");
-
-        Ok(Self {
-            relationship_comp,
-            component_storage_lock,
-        })
-    }
-}
-
-impl<'rel_comp, Kind, ComponentT> Relation<'rel_comp, Kind, ComponentT>
-where
-    ComponentT: Component,
-{
-    /// Returns the component of the target at the specified index.
-    ///
-    /// # Panics
-    /// Will panic if the entity does not exist in the archetype it belongs to. This
-    /// should hopefully never happend.
-    #[must_use]
-    pub fn get(&self, index: usize) -> Option<ComponentHandle<'_, ComponentT>>
-    {
-        let target = self.get_target(index)?;
-
-        let archetype = self.component_storage_lock.get_entity_archetype(*target)?;
-
-        let entity = archetype
-            .get_entity_by_id(*target)
-            .expect("Target entity is gone from archetype");
-
-        let component_index = archetype.get_index_for_component(ComponentT::id())?;
-
-        let component = ComponentHandle::new(
-            entity
-                .components()
-                .get(component_index)?
-                .component()
-                .read_nonblock()
-                .unwrap_or_else(|_| {
-                    panic!(
-                        "Failed to aquire read-write lock of component {}",
-                        type_name::<ComponentT>()
-                    )
-                }),
-        );
-
-        Some(component)
-    }
-
-    /// Returns a reference to the target at the specified index.
-    #[must_use]
-    pub fn get_target(&self, index: usize) -> Option<&Uid>
-    {
-        match &self.relationship_comp.entity_uid {
-            SingleOrMultiple::Single(entity_uid) if index == 0 => Some(entity_uid),
-            SingleOrMultiple::Multiple(entity_uids) => entity_uids.get(index),
-            SingleOrMultiple::Single(_) => None,
-        }
-    }
-
-    #[must_use]
-    pub fn target_count(&self) -> usize
-    {
-        match &self.relationship_comp.entity_uid {
-            SingleOrMultiple::Single(_) => 1,
-            SingleOrMultiple::Multiple(entity_uids) => entity_uids.len(),
-        }
-    }
-
-    pub fn target_uids(&self) -> impl Iterator<Item = Uid> + '_
-    {
-        (0..self.target_count())
-            .map_while(|target_index| self.get_target(target_index).copied())
-    }
-
-    /// Returns a iterator of the components of the targets of this relationship.
-    #[must_use]
-    pub fn iter(&self) -> TargetComponentIter<'_, 'rel_comp, Kind, ComponentT>
-    {
-        TargetComponentIter { relation: self, index: 0 }
-    }
-}
-
-impl<'relationship, 'rel_comp, Kind, ComponentT> IntoIterator
-    for &'relationship Relation<'rel_comp, Kind, ComponentT>
-where
-    'relationship: 'rel_comp,
-    ComponentT: Component,
-{
-    type IntoIter = TargetComponentIter<'relationship, 'rel_comp, Kind, ComponentT>;
-    type Item = ComponentHandle<'rel_comp, ComponentT>;
-
-    fn into_iter(self) -> Self::IntoIter
-    {
-        self.iter()
-    }
-}
-
-/// Iterator of the components of the targets of a relationship.
-pub struct TargetComponentIter<'relationship, 'rel_comp, Kind, ComponentT>
-where
-    Kind: 'static,
-    ComponentT: Component,
-{
-    relation: &'relationship Relation<'rel_comp, Kind, ComponentT>,
-    index: usize,
-}
-
-impl<'relationship, 'rel_comp, Kind, ComponentT> Iterator
-    for TargetComponentIter<'relationship, 'rel_comp, Kind, ComponentT>
-where
-    'relationship: 'rel_comp,
-    Kind: 'static,
-    ComponentT: Component,
-{
-    type Item = ComponentHandle<'rel_comp, ComponentT>;
-
-    fn next(&mut self) -> Option<Self::Item>
-    {
-        let index = self.index;
-
-        self.index += 1;
-
-        self.relation.get(index)
-    }
-}
-
-/// Relationship kind denoting a dependency to another entity
-#[derive(Debug, Default, Clone, Copy)]
-pub struct DependsOn;
-
-/// Relationship kind denoting being the child of another entity.
-#[derive(Debug, Default, Clone, Copy)]
-pub struct ChildOf;
diff --git a/ecs/src/uid.rs b/ecs/src/uid.rs
index c3ed85b..6c97649 100644
--- a/ecs/src/uid.rs
+++ b/ecs/src/uid.rs
@@ -1,23 +1,28 @@
-use std::fmt::{Debug, Formatter};
+use std::fmt::{Debug, Display, Formatter};
 use std::mem::transmute;
 use std::sync::atomic::{AtomicU32, Ordering};
 
+use crate::component::Component;
 use crate::util::{gen_mask_64, BitMask, NumberExt};
 
-static NEXT: AtomicU32 = AtomicU32::new(1);
+static NEXT: AtomicU32 = AtomicU32::new(Uid::FIRST_UNIQUE_ID);
+
+static WILDCARD_ID: u32 = 1;
 
 const ID_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(32..=63));
+const RELATION_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(6..=31));
 const KIND_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(0..=1));
 
 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
 #[repr(u8)]
 pub enum Kind
 {
+    Pair = 3,
     Entity = 2,
     Component = 1,
 }
 
-/// Unique entity/component ID.
+/// A unique identifier.
 #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
 pub struct Uid
 {
@@ -26,6 +31,10 @@ pub struct Uid
 
 impl Uid
 {
+    /// The id part of the first unique `Uid`. The ids `0..Uid::FIRST_UNIQUE_ID` are
+    /// reserved.
+    pub const FIRST_UNIQUE_ID: u32 = 5;
+
     /// Returns a new unique entity/component ID.
     pub fn new_unique(kind: Kind) -> Self
     {
@@ -36,6 +45,37 @@ impl Uid
         }
     }
 
+    #[must_use]
+    pub fn wildcard() -> Self
+    {
+        Self {
+            inner: ID_BITS.field_prep(u64::from(WILDCARD_ID))
+                | KIND_BITS.field_prep(Kind::Component as u64),
+        }
+    }
+
+    #[must_use]
+    pub fn new_pair(params: PairParams) -> Self
+    {
+        assert_ne!(
+            params.relation.kind(),
+            Kind::Pair,
+            "Pair relation cannot be a pair"
+        );
+
+        assert_ne!(
+            params.target.kind(),
+            Kind::Pair,
+            "Pair target cannot be a pair"
+        );
+
+        Self {
+            inner: ID_BITS.field_prep(u64::from(params.target.id()))
+                | RELATION_BITS.field_prep(u64::from(params.relation.id()))
+                | KIND_BITS.field_prep(Kind::Pair as u64),
+        }
+    }
+
     #[must_use]
     pub fn id(&self) -> u32
     {
@@ -57,6 +97,76 @@ impl Uid
         // in the new_unique function
         unsafe { transmute::<u8, Kind>(kind) }
     }
+
+    /// If this `Uid` is a pair, returns the relation as a component `Uid`.
+    ///
+    /// # Panics
+    /// Will panic if this `Uid` is not a pair.
+    pub fn relation_component(&self) -> Self
+    {
+        assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair");
+
+        Self {
+            inner: ID_BITS.field_prep(u64::from(self.relation()))
+                | KIND_BITS.field_prep(Kind::Component as u64),
+        }
+    }
+
+    pub fn has_same_relation_as(&self, other: Self) -> bool
+    {
+        self.relation() == other.relation()
+    }
+
+    /// If this `Uid` is a pair, returns the relation as a entity `Uid`.
+    ///
+    /// # Panics
+    /// Will panic if this `Uid` is not a pair.
+    pub fn relation_entity(&self) -> Self
+    {
+        assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair");
+
+        Self {
+            inner: ID_BITS.field_prep(u64::from(self.relation()))
+                | KIND_BITS.field_prep(Kind::Entity as u64),
+        }
+    }
+
+    /// If this `Uid` is a pair, returns the target as a component `Uid`.
+    ///
+    /// # Panics
+    /// Will panic if this `Uid` is not a pair.
+    pub fn target_component(&self) -> Self
+    {
+        assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair");
+
+        Self {
+            inner: ID_BITS.field_prep(u64::from(self.id()))
+                | KIND_BITS.field_prep(Kind::Component as u64),
+        }
+    }
+
+    /// If this `Uid` is a pair, returns the target as a entity `Uid`.
+    ///
+    /// # Panics
+    /// Will panic if this `Uid` is not a pair.
+    pub fn target_entity(&self) -> Self
+    {
+        assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair");
+
+        Self {
+            inner: ID_BITS.field_prep(u64::from(self.id()))
+                | KIND_BITS.field_prep(Kind::Entity as u64),
+        }
+    }
+
+    fn relation(&self) -> u32
+    {
+        let Ok(relation) = u32::try_from(self.inner.field_get(RELATION_BITS)) else {
+            unreachable!("Uid relation does not fit in u32");
+        };
+
+        relation
+    }
 }
 
 impl Debug for Uid
@@ -70,3 +180,55 @@ impl Debug for Uid
             .finish_non_exhaustive()
     }
 }
+
+impl Display for Uid
+{
+    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result
+    {
+        if self.kind() == Kind::Pair {
+            return write!(
+                formatter,
+                "({}, {})",
+                self.relation(),
+                self.target_component()
+            );
+        }
+
+        if *self == Uid::wildcard() {
+            return write!(formatter, "*");
+        }
+
+        write!(formatter, "{}", self.id())
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct PairParams
+{
+    pub relation: Uid,
+    pub target: Uid,
+}
+
+pub trait With: 'static
+{
+    fn uid() -> Uid;
+}
+
+impl<ComponentT: Component> With for ComponentT
+{
+    fn uid() -> Uid
+    {
+        Self::id()
+    }
+}
+
+#[derive(Debug)]
+pub enum Wildcard {}
+
+impl With for Wildcard
+{
+    fn uid() -> Uid
+    {
+        Uid::wildcard()
+    }
+}
diff --git a/ecs/tests/query.rs b/ecs/tests/query.rs
index 6e747e3..062fd5a 100644
--- a/ecs/tests/query.rs
+++ b/ecs/tests/query.rs
@@ -36,13 +36,13 @@ struct G;
 fn setup()
 {
     SETUP.call_once_force(|_| {
-        assert_eq!(A::id().id(), 1);
-        assert_eq!(B::id().id(), 2);
-        assert_eq!(C::id().id(), 3);
-        assert_eq!(D::id().id(), 4);
-        assert_eq!(E::id().id(), 5);
-        assert_eq!(F::id().id(), 6);
-        assert_eq!(G::id().id(), 7);
+        assert_eq!(A::id().id(), Uid::FIRST_UNIQUE_ID);
+        assert_eq!(B::id().id(), Uid::FIRST_UNIQUE_ID + 1);
+        assert_eq!(C::id().id(), Uid::FIRST_UNIQUE_ID + 2);
+        assert_eq!(D::id().id(), Uid::FIRST_UNIQUE_ID + 3);
+        assert_eq!(E::id().id(), Uid::FIRST_UNIQUE_ID + 4);
+        assert_eq!(F::id().id(), Uid::FIRST_UNIQUE_ID + 5);
+        assert_eq!(G::id().id(), Uid::FIRST_UNIQUE_ID + 6);
     });
 }
 
-- 
cgit v1.2.3-18-g5258