Skip to main content

mittens_engine/engine/ecs/component/
component_ref.rs

1//! `ComponentRef` — an unresolved reference to another component, plus
2//! the surface form the author wrote.
3//!
4//! Used by components that hold pointers to other components in MMS
5//! source (`ActionComponent.target_sources`, `IKChainComponent.target_source`,
6//! `IKChainComponent.end_effector_source`, …). The two variants cover the
7//! two durable on-disk forms; whichever was authored is preserved
8//! verbatim through dump so save → reload reproduces the original
9//! source.
10//!
11//! - `Guid(uuid)` — author wrote `@uuid:<hex>` OR passed a live
12//!   `Value::ComponentObject` (let-bound / query result), which the
13//!   registry collapses to the target's guid at call-construction time.
14//!   Resolution at runtime is an O(1) hashmap hit via
15//!   `World::component_id_by_guid`.
16//! - `Query(selector)` — any other selector string (`#name`,
17//!   `[attr=value]`, `../#name`, `/#name`, ...). Preserved as-is;
18//!   resolution may treat a leading root prefix as a scope override.
19//!
20//! Resolution is typically deferred — components carry both a
21//! `ComponentRef` (for dump) and a resolved `ComponentId` (for runtime).
22//! A system pass fills the resolved id when the referent is reachable;
23//! both `AnimationSystem` (for Action) and `IKSystem` (for IKChain) do
24//! this just before consuming the resolved id.
25
26use crate::engine::ecs::{ComponentId, World};
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum ComponentRef {
30    Guid(uuid::Uuid),
31    Query(String),
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum QueryRootMode {
36    SelfSubtree,
37    ParentScope { levels_up: usize },
38    WorldRoot,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct ScopedQuery<'a> {
43    pub root_mode: QueryRootMode,
44    pub selector: &'a str,
45}
46
47pub fn parse_scoped_query(input: &str) -> ScopedQuery<'_> {
48    let mut rest = input;
49    let mut levels_up = 0usize;
50    while let Some(next) = rest.strip_prefix("../") {
51        levels_up += 1;
52        rest = next.trim_start();
53    }
54    if levels_up > 0 {
55        return ScopedQuery {
56            root_mode: QueryRootMode::ParentScope { levels_up },
57            selector: rest,
58        };
59    }
60    if let Some(next) = rest.strip_prefix('/') {
61        return ScopedQuery {
62            root_mode: QueryRootMode::WorldRoot,
63            selector: next.trim_start(),
64        };
65    }
66    ScopedQuery {
67        root_mode: QueryRootMode::SelfSubtree,
68        selector: input,
69    }
70}
71
72pub fn resolve_component_ref(
73    world: &World,
74    src: &ComponentRef,
75    owner: Option<ComponentId>,
76    default_root_mode: QueryRootMode,
77) -> Option<ComponentId> {
78    match src {
79        ComponentRef::Guid(uuid) => world.component_id_by_guid(*uuid),
80        ComponentRef::Query(query) => resolve_scoped_query(world, owner, query, default_root_mode),
81    }
82}
83
84pub fn resolve_scoped_query(
85    world: &World,
86    owner: Option<ComponentId>,
87    query: &str,
88    default_root_mode: QueryRootMode,
89) -> Option<ComponentId> {
90    let scoped = parse_scoped_query(query);
91    let root_mode = match scoped.root_mode {
92        QueryRootMode::SelfSubtree if query == scoped.selector => default_root_mode,
93        other => other,
94    };
95    let selector = scoped.selector.trim();
96    if selector.is_empty() {
97        return None;
98    }
99
100    match root_mode {
101        QueryRootMode::SelfSubtree => {
102            let root = owner?;
103            world.find_component(root, selector)
104        }
105        QueryRootMode::ParentScope { levels_up } => {
106            let root = climb_scope(world, owner?, levels_up)?;
107            world.find_component(root, selector)
108        }
109        QueryRootMode::WorldRoot => resolve_world_root_query(world, selector),
110    }
111}
112
113fn climb_scope(world: &World, mut current: ComponentId, levels_up: usize) -> Option<ComponentId> {
114    for _ in 0..levels_up {
115        current = world.parent_of(current)?;
116    }
117    Some(current)
118}
119
120fn resolve_world_root_query(world: &World, selector: &str) -> Option<ComponentId> {
121    if let Some(stripped) = selector.strip_prefix('>') {
122        let child_selector = stripped.trim_start();
123        if child_selector.is_empty() {
124            return None;
125        }
126        return world
127            .world_roots()
128            .into_iter()
129            .find(|&root| world.component_matches_selector(root, child_selector));
130    }
131    world
132        .world_roots()
133        .into_iter()
134        .find_map(|root| world.find_component(root, selector))
135}