mittens_engine/engine/ecs/system/
router_system.rs1use 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}