Skip to main content

mittens_engine/engine/ecs/system/
router_system.rs

1use crate::engine::ecs::component::{RouterComponent, TransformComponent};
2use crate::engine::ecs::rx::RxWorld;
3use crate::engine::ecs::{ComponentId, EventSignal, IntentValue, SignalEmitter, SignalKind, World};
4use std::collections::HashSet;
5
6#[derive(Debug, Default)]
7pub struct RouterSystem {
8    observed_owners: HashSet<ComponentId>,
9}
10
11#[derive(Debug)]
12struct ResolvedRouter {
13    router_component: ComponentId,
14    target_component: ComponentId,
15    ignored_components: HashSet<ComponentId>,
16}
17
18impl RouterSystem {
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    pub fn register_router(
24        &mut self,
25        rx: &mut RxWorld,
26        world: &mut World,
27        emit: &mut dyn SignalEmitter,
28        router_component: ComponentId,
29    ) {
30        let Some(owner) = world.parent_of(router_component) else {
31            return;
32        };
33
34        if Self::immediate_router_child(world, owner) != Some(router_component) {
35            return;
36        }
37
38        if self.observed_owners.insert(owner) {
39            rx.add_handler_closure(SignalKind::ParentChanged, owner, move |world, emit, env| {
40                let Some(EventSignal::ParentChanged {
41                    child, new_parent, ..
42                }) = env.event.as_ref()
43                else {
44                    return;
45                };
46
47                if *new_parent != Some(owner) {
48                    return;
49                }
50
51                Self::route_external_child(world, emit, owner, *child);
52            });
53        }
54
55        Self::reroute_owner_children(world, emit, owner);
56    }
57
58    fn immediate_router_child(world: &World, owner: ComponentId) -> Option<ComponentId> {
59        world.children_of(owner).iter().copied().find(|&child| {
60            world
61                .get_component_by_id_as::<RouterComponent>(child)
62                .is_some()
63        })
64    }
65
66    fn reroute_owner_children(world: &mut World, emit: &mut dyn SignalEmitter, owner: ComponentId) {
67        let children: Vec<ComponentId> = world.children_of(owner).to_vec();
68        for child in children {
69            Self::route_external_child(world, emit, owner, child);
70        }
71    }
72
73    fn route_external_child(
74        world: &mut World,
75        emit: &mut dyn SignalEmitter,
76        owner: ComponentId,
77        child: ComponentId,
78    ) {
79        let Some(router) = Self::resolve_router(world, owner) else {
80            return;
81        };
82
83        if !Self::is_external_direct_child(world, owner, child, &router) {
84            return;
85        }
86
87        if Self::subtree_contains(world, child, router.target_component) {
88            println!(
89                "[RouterSystem] refusing to route child={:?} into descendant target={:?}",
90                child, router.target_component
91            );
92            return;
93        }
94
95        emit.push_intent_now(
96            child,
97            IntentValue::Attach {
98                parents: vec![router.target_component],
99                child,
100            },
101        );
102    }
103
104    fn resolve_router(world: &World, owner: ComponentId) -> Option<ResolvedRouter> {
105        let router_component = Self::immediate_router_child(world, owner)?;
106        let router = world.get_component_by_id_as::<RouterComponent>(router_component)?;
107        let target_name = router.target_name.as_deref()?;
108        let target_component = Self::find_first_named_in_subtree(world, owner, target_name)?;
109
110        let mut ignored_components = HashSet::new();
111        for ignore_name in &router.ignore_names {
112            Self::collect_named_in_subtree(world, owner, ignore_name, &mut ignored_components);
113        }
114
115        Some(ResolvedRouter {
116            router_component,
117            target_component,
118            ignored_components,
119        })
120    }
121
122    fn is_external_direct_child(
123        world: &World,
124        owner: ComponentId,
125        child: ComponentId,
126        router: &ResolvedRouter,
127    ) -> bool {
128        if world.parent_of(child) != Some(owner) {
129            return false;
130        }
131        if child == router.router_component || child == router.target_component {
132            return false;
133        }
134        if router.ignored_components.contains(&child) {
135            return false;
136        }
137        if world
138            .get_component_by_id_as::<TransformComponent>(child)
139            .is_none()
140        {
141            return false;
142        }
143        if world
144            .component_label(child)
145            .map(|label| label.starts_with("__"))
146            .unwrap_or(false)
147        {
148            return false;
149        }
150        true
151    }
152
153    fn find_first_named_in_subtree(
154        world: &World,
155        root: ComponentId,
156        wanted_name: &str,
157    ) -> Option<ComponentId> {
158        let mut stack = vec![root];
159        while let Some(node) = stack.pop() {
160            if world.component_label(node) == Some(wanted_name) {
161                return Some(node);
162            }
163            for &child in world.children_of(node).iter().rev() {
164                stack.push(child);
165            }
166        }
167        None
168    }
169
170    fn collect_named_in_subtree(
171        world: &World,
172        root: ComponentId,
173        wanted_name: &str,
174        out: &mut HashSet<ComponentId>,
175    ) {
176        let mut stack = vec![root];
177        while let Some(node) = stack.pop() {
178            if world.component_label(node) == Some(wanted_name) {
179                out.insert(node);
180            }
181            for &child in world.children_of(node).iter().rev() {
182                stack.push(child);
183            }
184        }
185    }
186
187    fn subtree_contains(world: &World, root: ComponentId, wanted: ComponentId) -> bool {
188        let mut stack = vec![root];
189        while let Some(node) = stack.pop() {
190            if node == wanted {
191                return true;
192            }
193            for &child in world.children_of(node).iter().rev() {
194                stack.push(child);
195            }
196        }
197        false
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use crate::engine::ecs::component::{RouterComponent, StyleComponent, TransformComponent};
204    use crate::engine::ecs::{CommandQueue, IntentValue, SignalEmitter, SystemWorld, World};
205    use crate::engine::graphics::{RenderAssets, VisualWorld};
206
207    #[test]
208    fn router_reroutes_initial_direct_children_to_target() {
209        let mut world = World::default();
210        let mut visuals = VisualWorld::new();
211        let mut render_assets = RenderAssets::new();
212        let mut systems = SystemWorld::default();
213        let mut queue = CommandQueue::new();
214
215        let owner = world.add_component_boxed_named("owner", Box::new(TransformComponent::new()));
216        let router = world.add_component_boxed_named(
217            "router",
218            Box::new(
219                RouterComponent::new()
220                    .with_target_name("container")
221                    .with_ignored_names(["toolbar"]),
222            ),
223        );
224        let toolbar =
225            world.add_component_boxed_named("toolbar", Box::new(TransformComponent::new()));
226        let container =
227            world.add_component_boxed_named("container", Box::new(TransformComponent::new()));
228        let authored =
229            world.add_component_boxed_named("authored", Box::new(TransformComponent::new()));
230
231        let _ = world.add_child(owner, router);
232        let _ = world.add_child(owner, toolbar);
233        let _ = world.add_child(owner, container);
234        let _ = world.add_child(owner, authored);
235
236        world.init_component_tree(owner, &mut queue);
237        systems.process_commands(&mut world, &mut visuals, &mut render_assets, &mut queue);
238
239        assert_eq!(world.parent_of(authored), Some(container));
240        assert_eq!(world.parent_of(toolbar), Some(owner));
241    }
242
243    #[test]
244    fn router_reroutes_late_attached_children_to_target() {
245        let mut world = World::default();
246        let mut visuals = VisualWorld::new();
247        let mut render_assets = RenderAssets::new();
248        let mut systems = SystemWorld::default();
249        let mut queue = CommandQueue::new();
250
251        let owner = world.add_component_boxed_named("owner", Box::new(TransformComponent::new()));
252        let router = world.add_component_boxed_named(
253            "router",
254            Box::new(RouterComponent::new().with_target_name("container")),
255        );
256        let container =
257            world.add_component_boxed_named("container", Box::new(TransformComponent::new()));
258
259        let _ = world.add_child(owner, router);
260        let _ = world.add_child(owner, container);
261
262        world.init_component_tree(owner, &mut queue);
263        systems.process_commands(&mut world, &mut visuals, &mut render_assets, &mut queue);
264
265        let late = world.add_component_boxed_named("late", Box::new(TransformComponent::new()));
266        queue.push_intent_now(
267            late,
268            IntentValue::Attach {
269                parents: vec![owner],
270                child: late,
271            },
272        );
273
274        systems.process_commands(&mut world, &mut visuals, &mut render_assets, &mut queue);
275
276        assert_eq!(world.parent_of(late), Some(container));
277    }
278
279    #[test]
280    fn router_does_not_reroute_non_transform_children() {
281        let mut world = World::default();
282        let mut visuals = VisualWorld::new();
283        let mut render_assets = RenderAssets::new();
284        let mut systems = SystemWorld::default();
285        let mut queue = CommandQueue::new();
286
287        let owner = world.add_component_boxed_named("owner", Box::new(TransformComponent::new()));
288        let router = world.add_component_boxed_named(
289            "router",
290            Box::new(RouterComponent::new().with_target_name("container")),
291        );
292        let container =
293            world.add_component_boxed_named("container", Box::new(TransformComponent::new()));
294        let style = world.add_component(StyleComponent::new());
295        let authored =
296            world.add_component_boxed_named("authored", Box::new(TransformComponent::new()));
297
298        let _ = world.add_child(owner, router);
299        let _ = world.add_child(owner, container);
300        let _ = world.add_child(owner, style);
301        let _ = world.add_child(owner, authored);
302
303        world.init_component_tree(owner, &mut queue);
304        systems.process_commands(&mut world, &mut visuals, &mut render_assets, &mut queue);
305
306        assert_eq!(world.parent_of(authored), Some(container));
307        assert_eq!(world.parent_of(style), Some(owner));
308    }
309}