1use crate::engine::ecs::component::{
2 AnimationState, RenderableComponent, TextComponent, TransformComponent,
3};
4use crate::engine::ecs::system::SystemWorld;
5use crate::engine::ecs::{ComponentId, EventSignal, IntentValue, Signal, SignalEmitter, World};
6use crate::engine::graphics::VisualWorld;
7
8#[derive(Debug, Default)]
13pub struct RxMutationExecutor;
14
15impl RxMutationExecutor {
16 pub fn new() -> Self {
17 Self::default()
18 }
19
20 pub fn execute(
21 &mut self,
22 systems: &mut SystemWorld,
23 world: &mut World,
24 visuals: &mut VisualWorld,
25 render_assets: &mut crate::engine::graphics::RenderAssets,
26 emit: &mut dyn SignalEmitter,
27 env: &Signal,
28 ) {
29 use crate::engine::ecs::system::audio_system::AudioOp;
30 use crate::engine::graphics::primitives::Transform;
31
32 fn collect_text_targets(world: &World, target: ComponentId, out: &mut Vec<ComponentId>) {
33 if world
34 .get_component_by_id_as::<TextComponent>(target)
35 .is_some()
36 {
37 out.push(target);
38 return;
39 }
40
41 let mut stack = vec![target];
42 while let Some(node) = stack.pop() {
43 for &ch in world.children_of(node) {
44 stack.push(ch);
45 }
46
47 if world
48 .get_component_by_id_as::<TextComponent>(node)
49 .is_some()
50 {
51 out.push(node);
52 }
53 }
54 }
55
56 fn apply_set_text_to_component(
57 systems: &mut SystemWorld,
58 world: &mut World,
59 visuals: &mut VisualWorld,
60 emit: &mut dyn SignalEmitter,
61 component: ComponentId,
62 text: &String,
63 ) {
64 if let Some(tc) = world.get_component_by_id_as_mut::<TextComponent>(component) {
66 tc.text = text.clone();
67 tc.mark_unbuilt();
68
69 let children: Vec<ComponentId> = world.children_of(component).to_vec();
71 for ch in children {
72 if world
73 .get_component_by_id_as::<TransformComponent>(ch)
74 .is_none()
75 {
76 continue;
77 }
78
79 let has_renderable_child = world.children_of(ch).iter().any(|&gch| {
80 world
81 .get_component_by_id_as::<RenderableComponent>(gch)
82 .is_some()
83 });
84 if has_renderable_child {
85 systems.remove_subtree_immediate(world, visuals, ch);
87 }
88 }
89 }
90
91 systems.register_text(world, visuals, component, emit);
92 }
93
94 let Some(intent) = env.intent.as_ref() else {
95 return;
96 };
97
98 match &intent.value {
99 IntentValue::RegisterRenderable { component_ids } => {
100 for &component in component_ids.iter() {
101 systems.register_renderable(world, visuals, component);
102 }
103 }
104 IntentValue::RemoveRenderable { component_ids } => {
105 for &component in component_ids.iter() {
106 systems.remove_renderable(world, visuals, component);
107 }
108 }
109 IntentValue::RegisterStencilClip { component_ids } => {
110 for &component in component_ids.iter() {
111 systems.register_stencil_clip(world, visuals, component);
112 }
113 }
114 IntentValue::UnregisterStencilClip { component_ids } => {
115 for &component in component_ids.iter() {
116 systems.unregister_stencil_clip(world, visuals, component);
117 }
118 }
119 IntentValue::RegisterRouter { component_ids } => {
120 for &component in component_ids.iter() {
121 systems.register_router(world, emit, component);
122 }
123 }
124 IntentValue::RegisterHttpServer { component_ids } => {
125 for &component in component_ids.iter() {
126 systems.register_http_server(world, emit, component);
127 }
128 }
129 IntentValue::RegisterHttpClient { component_ids } => {
130 for &component in component_ids.iter() {
131 systems.register_http_client(world, emit, component);
132 }
133 }
134 IntentValue::HttpClientRequest {
135 component_id,
136 method,
137 url,
138 headers,
139 body_text,
140 } => {
141 systems.http_client.issue_request(
142 *component_id,
143 method.clone(),
144 url.clone(),
145 headers.clone(),
146 body_text.clone(),
147 );
148 }
149 IntentValue::HttpServerReply {
150 component_id,
151 request_id,
152 status,
153 headers,
154 body_text,
155 } => {
156 systems.http_server.deliver_reply(
157 *component_id,
158 *request_id,
159 *status,
160 headers.clone(),
161 body_text.clone(),
162 );
163 }
164 IntentValue::RegisterScrolling { component_ids } => {
165 for &component in component_ids.iter() {
166 systems.register_scrolling(world, emit, component);
167 }
168 }
169
170 IntentValue::RegisterTransform { component_ids } => {
171 for &component in component_ids.iter() {
172 systems.transform_changed(world, visuals, component);
173 }
174 }
175 IntentValue::UpdateTransformWorld { component_ids } => {
176 for &component in component_ids.iter() {
177 systems.transform_changed(world, visuals, component);
178 }
179 }
180 IntentValue::UpdateTransform {
181 component_ids,
182 translation,
183 rotation_quat_xyzw,
184 scale,
185 } => {
186 let mut t = Transform::default();
187 t.translation = *translation;
188 t.rotation = *rotation_quat_xyzw;
189 t.scale = *scale;
190 t.recompute_model();
191
192 for &component in component_ids.iter() {
193 systems.update_transform(world, visuals, component, t);
194 }
195 }
196 IntentValue::RemoveTransform { component_ids } => {
197 for &component in component_ids.iter() {
198 systems.remove_transform(world, visuals, component);
199 }
200 }
201
202 IntentValue::RegisterCamera3d { component_ids } => {
203 for &component in component_ids.iter() {
204 systems.register_camera(world, visuals, component);
205 }
206 }
207 IntentValue::RegisterCamera2d { component_ids } => {
208 for &component in component_ids.iter() {
209 systems.register_camera2d(world, visuals, component);
210 }
211 }
212 IntentValue::MakeActiveCamera { component_ids } => {
213 for &component in component_ids.iter() {
214 systems.make_active_camera(world, visuals, component);
215 }
216 }
217
218 IntentValue::RegisterInput { component_ids } => {
219 for &component in component_ids.iter() {
220 systems.register_input(component);
221 }
222 }
223 IntentValue::RegisterUv { component_ids } => {
224 for &component in component_ids.iter() {
225 systems.register_uv(world, visuals, component);
226 }
227 }
228
229 IntentValue::RegisterLight { component_ids } => {
230 for &component in component_ids.iter() {
231 systems.register_light(world, visuals, component);
232 }
233 }
234 IntentValue::RegisterColor { component_ids } => {
235 for &component in component_ids.iter() {
236 systems.register_color(world, visuals, component);
237 }
238 }
239 IntentValue::RegisterOpacity { component_ids } => {
240 for &component in component_ids.iter() {
241 systems.register_opacity(world, visuals, component);
242 }
243 }
244 IntentValue::RegisterTransparentCutout { component_ids } => {
245 for &component in component_ids.iter() {
246 systems.register_transparent_cutout(world, visuals, component);
247 }
248 }
249 IntentValue::RegisterBackgroundColor { component_ids } => {
250 for &component in component_ids.iter() {
251 systems.register_background_color(world, visuals, component);
252 }
253 }
254 IntentValue::RegisterRendererSettings { component_ids } => {
255 for &component in component_ids.iter() {
256 systems.register_renderer_settings(world, visuals, component);
257 }
258 }
259 IntentValue::RegisterRenderGraph { component_ids } => {
260 for &component in component_ids.iter() {
261 systems.register_render_graph(world, visuals, component);
262 }
263 }
264 IntentValue::RegisterAmbientLight { component_ids } => {
265 for &component in component_ids.iter() {
266 systems.register_ambient_light(world, visuals, component);
267 }
268 }
269 IntentValue::RegisterEmissive { component_ids } => {
270 for &component in component_ids.iter() {
271 systems.register_emissive(world, visuals, component);
272 }
273 }
274 IntentValue::RegisterLightQuantization { component_ids } => {
275 for &component in component_ids.iter() {
276 systems.register_light_quantization(world, visuals, component);
277 }
278 }
279
280 IntentValue::RegisterTexture { component_ids } => {
281 for &component in component_ids.iter() {
282 systems.register_texture(world, visuals, component);
283 }
284 }
285 IntentValue::RegisterTextureFiltering { component_ids } => {
286 for &component in component_ids.iter() {
287 systems.register_texture_filtering(world, visuals, component);
288 }
289 }
290
291 IntentValue::RegisterText { component_ids } => {
292 for &component in component_ids.iter() {
293 systems.register_text(world, visuals, component, emit);
294 }
295 }
296 IntentValue::RegisterGLTF { component_ids } => {
297 for &component in component_ids.iter() {
298 systems.gltf.register_component(component);
299 }
300 }
301 IntentValue::RegisterTextInput { component_ids } => {
302 for &component in component_ids.iter() {
303 systems.register_text_input(world, component, emit);
304 }
305 }
306 IntentValue::SetText {
307 component_ids,
308 text,
309 } => {
310 let mut text_cids = Vec::new();
311 for &t in component_ids.iter() {
312 collect_text_targets(world, t, &mut text_cids);
313 }
314 text_cids.sort();
315 text_cids.dedup();
316
317 for text_cid in text_cids {
318 apply_set_text_to_component(systems, world, visuals, emit, text_cid, text);
319 }
320 }
321
322 IntentValue::SetEmissiveIntensity {
323 component_ids,
324 intensity,
325 } => {
326 let intensity = (*intensity).max(0.0);
327 for &cid in component_ids.iter() {
328 systems.update_emissive_intensity(world, visuals, cid, intensity);
329 }
330 }
331
332 IntentValue::SetLayoutAvailableWidth {
333 component_ids,
334 width,
335 } => {
336 use crate::engine::ecs::component::LayoutComponent;
337 let width = *width;
338 for &cid in component_ids.iter() {
339 if let Some(lo) = world.get_component_by_id_as_mut::<LayoutComponent>(cid) {
340 lo.set_available_width_dimension(width);
341 }
342 }
343 }
344
345 IntentValue::SetLayoutAvailableHeight {
346 component_ids,
347 height,
348 } => {
349 use crate::engine::ecs::component::LayoutComponent;
350 let height = *height;
351 for &cid in component_ids.iter() {
352 if let Some(lo) = world.get_component_by_id_as_mut::<LayoutComponent>(cid) {
353 lo.set_available_height_dimension(height);
354 }
355 }
356 }
357
358 IntentValue::SetLayoutInspect {
359 component_ids,
360 enabled,
361 } => {
362 use crate::engine::ecs::component::LayoutComponent;
363 let enabled = *enabled;
364 for &cid in component_ids.iter() {
365 if let Some(lo) = world.get_component_by_id_as_mut::<LayoutComponent>(cid) {
366 if lo.inspect != enabled {
367 lo.inspect = enabled;
368 lo.dirty = true;
369 }
370 }
371 }
372 }
373
374 IntentValue::TextInputSetFocus { .. }
375 | IntentValue::TextInputClearFocus
376 | IntentValue::TextInputInsertText { .. }
377 | IntentValue::TextInputBackspace
378 | IntentValue::TextInputDeleteForward
379 | IntentValue::TextInputMoveCaret { .. }
380 | IntentValue::TextInputMoveCaretTo { .. } => {
381 systems.text_input.execute_intent(world, emit, env);
382 }
383
384 IntentValue::RegisterCollision { component_ids } => {
385 for &component in component_ids.iter() {
386 systems.register_collision(world, visuals, component);
387 }
388 }
389 IntentValue::RemoveCollision { component_ids } => {
390 for &component in component_ids.iter() {
391 systems.remove_collision(world, visuals, component);
392 }
393 }
394 IntentValue::RegisterKineticResponse { component_ids } => {
395 for &component in component_ids.iter() {
396 systems.register_kinetic_response(world, visuals, component);
397 }
398 }
399 IntentValue::RemoveKineticResponse { component_ids } => {
400 for &component in component_ids.iter() {
401 systems.remove_kinetic_response(world, visuals, component);
402 }
403 }
404 IntentValue::RegisterAvatarControl { component_ids } => {
405 for &component in component_ids {
406 systems.register_avatar_control(component);
407 }
408 }
409 IntentValue::RegisterAvatarBodyYaw { component_ids } => {
410 for &component in component_ids {
411 systems.avatar_body_yaw.register(component);
412 }
413 }
414 IntentValue::RegisterIkChain { component_ids } => {
415 for &component in component_ids {
416 systems.ik.register(component);
417 }
418 }
419
420 IntentValue::RemoveSubtree { component_ids } => {
421 let mut roots: Vec<ComponentId> = component_ids.iter().copied().collect();
422 roots.sort();
423 roots.dedup();
424 for root in roots {
425 emit.push_intent_now(
426 root,
427 IntentValue::AudioGraphDirtyImmediate {
428 component_ids: vec![root],
429 },
430 );
431 if let Some(old_parent) = world.parent_of(root) {
434 world.detach_from_parent(root);
435 emit.push_event(
436 root,
437 EventSignal::ParentChanged {
438 child: root,
439 old_parent: Some(old_parent),
440 new_parent: None,
441 },
442 );
443 }
444 systems.remove_subtree_immediate(world, visuals, root);
445 }
446 }
447
448 IntentValue::RegisterXr { component_ids } => {
449 for &component in component_ids.iter() {
450 systems.register_xr(world, visuals, component);
451 }
452 }
453 IntentValue::RegisterInputXr { component_ids } => {
454 for &component in component_ids.iter() {
455 systems.register_input_xr(world, visuals, component);
456 }
457 }
458 IntentValue::RegisterControllerXr { component_ids } => {
459 for &component in component_ids.iter() {
460 systems.register_controller_xr(world, visuals, component);
461 }
462 }
463 IntentValue::RegisterInputXrGamepad { component_ids } => {
464 for &component in component_ids.iter() {
465 systems.register_input_xr_gamepad(world, visuals, component);
466 }
467 }
468 IntentValue::RemoveInputXr { component_ids } => {
469 for &component in component_ids.iter() {
470 systems.remove_input_xr(world, visuals, component);
471 }
472 }
473 IntentValue::RemoveControllerXr { component_ids } => {
474 for &component in component_ids.iter() {
475 systems.remove_controller_xr(world, visuals, component);
476 }
477 }
478 IntentValue::RemoveInputXrGamepad { component_ids } => {
479 for &component in component_ids.iter() {
480 systems.remove_input_xr_gamepad(world, visuals, component);
481 }
482 }
483
484 IntentValue::RegisterRaycast { component_ids } => {
485 for &component in component_ids.iter() {
486 systems.register_raycast(world, visuals, component);
487 }
488 }
489 IntentValue::RegisterPointer { component_ids } => {
490 for &component in component_ids.iter() {
491 systems.register_pointer(world, visuals, component, emit);
492 }
493 }
494 IntentValue::RemoveRaycast { component_ids } => {
495 for &component in component_ids.iter() {
496 systems.remove_raycast(world, visuals, component);
497 }
498 }
499
500 IntentValue::RegisterAnimation { component_ids } => {
501 for &component in component_ids.iter() {
502 systems.register_animation(world, visuals, component);
503 }
504 }
505 IntentValue::SetAnimationState {
506 component_ids,
507 state,
508 } => {
509 let state: AnimationState = state.clone();
510 for &component in component_ids.iter() {
511 systems.set_animation_state(component, state.clone());
512 }
513 }
514 IntentValue::RegisterKeyframe { component_ids } => {
515 for &component in component_ids.iter() {
516 systems.register_keyframe(world, visuals, component);
517 }
518 }
519
520 IntentValue::RegisterAudioOutput { component_ids } => {
521 for &component in component_ids.iter() {
522 systems.register_audio_output(world, visuals, component);
523 }
524 }
525 IntentValue::AudioGraphDirtyImmediate { component_ids } => {
526 for &component in component_ids.iter() {
527 systems.audio_graph_dirty(world, visuals, component);
528 }
529 }
530 IntentValue::RegisterAudioOscillator { component_ids } => {
531 for &component in component_ids.iter() {
532 systems.register_audio_oscillator(world, visuals, component);
533 }
534 }
535 IntentValue::RegisterAudioClip { component_ids } => {
536 for &component in component_ids.iter() {
537 systems.register_audio_clip(world, visuals, component);
538 }
539 }
540 IntentValue::RegisterAudioBufferSize { component_ids } => {
541 for &component in component_ids.iter() {
542 systems.register_audio_buffer_size(world, visuals, component);
543 }
544 }
545
546 IntentValue::RegisterClock { component_ids } => {
547 for &component in component_ids.iter() {
548 systems.register_clock(world, visuals, component);
549 }
550 }
551
552 IntentValue::RegisterTransformGizmo { component_ids } => {
553 for &component in component_ids.iter() {
554 systems.register_transform_gizmo(world, visuals, component, emit);
555 }
556 }
557
558 IntentValue::RegisterNormalVis { component_ids } => {
559 for &component in component_ids.iter() {
560 systems.register_normal_vis(world, component);
561 }
562 }
563
564 IntentValue::ReplExec { command } => {
565 systems.queue_repl_command(command.clone());
566 }
567
568 IntentValue::RegisterEditor { component_ids } => {
569 for &component in component_ids.iter() {
570 systems.register_editor(world, visuals, render_assets, component, emit);
571 }
572 }
573
574 IntentValue::RegisterAction { component_ids } => {
575 for &component in component_ids.iter() {
576 crate::engine::ecs::system::action_system::register_action(
577 world, emit, component,
578 );
579 }
580 }
581
582 IntentValue::RegisterSignalRouteUpward { component_ids } => {
583 for &component in component_ids.iter() {
584 systems.pipeline.register_signal_route_upward(
585 world,
586 &mut systems.rx,
587 component,
588 );
589 }
590 }
591 IntentValue::RemoveSignalRouteUpward { component_ids } => {
592 for &component in component_ids.iter() {
593 systems
594 .pipeline
595 .remove_signal_route_upward(&mut systems.rx, component);
596 }
597 }
598
599 IntentValue::ScheduleAudioOp {
600 component_ids,
601 beat,
602 op,
603 } => {
604 for &component in component_ids.iter() {
605 systems.audio.schedule_audio_op(component, *beat, *op);
606 }
607 }
608 IntentValue::ScheduleAudioGraphSwap {
609 component_ids,
610 beat,
611 } => {
612 for &component in component_ids.iter() {
613 systems.audio.schedule_graph_swap(&*world, component, *beat);
614 }
615 }
616 IntentValue::ScheduleAudioPitchSetHz {
617 component_ids,
618 beat,
619 frequency_hz,
620 } => {
621 for &component in component_ids.iter() {
622 systems.audio.schedule_audio_op(
623 component,
624 *beat,
625 AudioOp::SetHz(*frequency_hz),
626 );
627 }
628 }
629 IntentValue::ScheduleAudioOscillatorEnabled {
630 component_ids,
631 beat,
632 enabled,
633 } => {
634 for &component in component_ids.iter() {
635 systems.audio.schedule_audio_op(
636 component,
637 *beat,
638 AudioOp::SetEnabled(*enabled),
639 );
640 }
641 }
642 IntentValue::ScheduleAudioGainSet {
643 component_ids,
644 beat,
645 gain,
646 } => {
647 for &component in component_ids.iter() {
648 systems
649 .audio
650 .schedule_audio_op(component, *beat, AudioOp::SetGain(*gain));
651 }
652 }
653
654 IntentValue::PoseCapture { target, pose_name } => {
655 systems
656 .pose_capture
657 .handle_capture(world, emit, *target, pose_name.clone());
658 }
659 IntentValue::PoseApply { target, pose } => {
660 systems
661 .pose_capture
662 .handle_apply(world, emit, *target, *pose);
663 }
664
665 _ => {}
667 }
668 }
669}