1use crate::engine::ecs::{ComponentId, IntentValue, Signal, SignalEmitter, World};
2use crate::engine::graphics::RenderAssets;
3
4#[derive(Debug, Default)]
11pub struct RxIntentExecutor;
12
13impl RxIntentExecutor {
14 pub fn new() -> Self {
15 Self::default()
16 }
17
18 pub fn handles_value(value: &IntentValue) -> bool {
23 matches!(
27 value,
28 IntentValue::Noop
29 | IntentValue::SpawnComponentTree { .. }
30 | IntentValue::Print { .. }
31 | IntentValue::SetColor { .. }
32 | IntentValue::SetPosition { .. }
33 | IntentValue::LookAt { .. }
34 | IntentValue::GLTFArmatureVisible { .. }
35 | IntentValue::SelectionSet { .. }
36 | IntentValue::Attach { .. }
37 | IntentValue::QueryFindComponent { .. }
38 | IntentValue::QueryFindAllComponents { .. }
39 | IntentValue::AttachClone { .. }
40 | IntentValue::Detach { .. }
41 | IntentValue::RemoveChild { .. }
42 | IntentValue::RemoveChildren { .. }
43 | IntentValue::AudioGraphRebuild { .. }
44 | IntentValue::RequestRaycast { .. }
45 | IntentValue::AudioLowPassSetCutoffHz { .. }
46 | IntentValue::AudioBandPassSetCenterHz { .. }
47 | IntentValue::OscillatorSetEnabled { .. }
48 | IntentValue::OscillatorSetPitch { .. }
49 | IntentValue::OscillatorScheduleSetPitch { .. }
50 | IntentValue::AudioSchedulePlay { .. }
51 )
52 }
53
54 pub fn execute(
57 &mut self,
58 world: &mut World,
59 render_assets: &mut RenderAssets,
60 emit: &mut dyn SignalEmitter,
61 env: &Signal,
62 ) {
63 handle_intent_signal(world, render_assets, emit, env);
64 }
65}
66
67fn handle_intent_signal(
68 world: &mut World,
69 render_assets: &mut RenderAssets,
70 emit: &mut dyn SignalEmitter,
71 env: &Signal,
72) {
73 use crate::engine::ecs::component::{
74 AudioBandPassFilterComponent, AudioClipComponent, AudioClipLoadState,
75 AudioLowPassFilterComponent, AudioOscillatorComponent, ColorComponent, MusicNoteComponent,
76 RayCastComponent, TransformComponent,
77 };
78 use crate::engine::ecs::system::MusicSystem;
79 use crate::engine::ecs::system::audio_system::AudioOp;
80 use crate::engine::ecs::{ComponentId, EventSignal, IntentValue};
81
82 let beat_now = 0.0;
83
84 let Some(intent) = env.intent.as_ref() else {
85 return;
86 };
87
88 match &intent.value {
89 IntentValue::Noop => {}
90
91 IntentValue::SpawnComponentTree { root, parent } => {
92 match crate::scripting::component_registry::with_live_render_assets(
93 render_assets,
94 || crate::scripting::component_registry::spawn_tree(root, *parent, world, emit),
95 ) {
96 Ok(id) => println!("[SpawnComponentTree] spawned root {id:?}"),
97 Err(e) => println!("[SpawnComponentTree] error: {e}"),
98 }
99 }
100
101 IntentValue::Print { .. } => {}
102
103 IntentValue::SetColor {
104 component_ids,
105 rgba,
106 } => {
107 let mut color_cids = Vec::new();
108 for &t in component_ids.iter() {
109 collect_color_targets(world, t, &mut color_cids);
110 }
111 color_cids.sort();
112 for color_cid in color_cids {
113 if let Some(c) = world.get_component_by_id_as_mut::<ColorComponent>(color_cid) {
114 c.rgba = *rgba;
115 emit.push_intent_now(
116 color_cid,
117 IntentValue::RegisterColor {
118 component_ids: vec![color_cid],
119 },
120 );
121 }
122 }
123 }
124
125 IntentValue::SetText { .. } => {
126 }
128
129 IntentValue::SetPosition {
130 component_ids,
131 position,
132 } => {
133 let mut transform_cids = Vec::new();
134 for &t in component_ids.iter() {
135 collect_transform_targets(world, t, &mut transform_cids);
136 }
137 transform_cids.sort();
138 transform_cids.dedup();
139 for transform_cid in transform_cids {
140 if let Some(t) =
141 world.get_component_by_id_as_mut::<TransformComponent>(transform_cid)
142 {
143 t.set_position(emit, position[0], position[1], position[2]);
144 }
145 }
146 }
147
148 IntentValue::LookAt {
149 component_ids,
150 target_world,
151 } => {
152 let mut transform_cids = Vec::new();
153 for &t in component_ids.iter() {
154 collect_transform_targets(world, t, &mut transform_cids);
155 }
156 transform_cids.sort();
157 transform_cids.dedup();
158 for transform_cid in transform_cids {
159 let Some(world_position) =
160 crate::engine::ecs::system::TransformSystem::world_position(
161 world,
162 transform_cid,
163 )
164 else {
165 continue;
166 };
167
168 let Some(desired_world_rotation) =
169 TransformComponent::look_at_world_rotation(world_position, *target_world)
170 else {
171 continue;
172 };
173
174 let parent_world_rotation = world
175 .parent_of(transform_cid)
176 .and_then(|parent| {
177 crate::engine::ecs::system::TransformSystem::world_model(world, parent)
178 })
179 .map(crate::utils::math::mat_to_quat)
180 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
181 let local_rotation =
182 crate::utils::math::quat_normalize(crate::utils::math::quat_mul(
183 crate::utils::math::quat_conjugate(parent_world_rotation),
184 desired_world_rotation,
185 ));
186
187 let Some(transform) = world
188 .get_component_by_id_as::<TransformComponent>(transform_cid)
189 .map(|t| t.transform)
190 else {
191 continue;
192 };
193
194 emit.push_intent_now(
195 transform_cid,
196 IntentValue::UpdateTransform {
197 component_ids: vec![transform_cid],
198 translation: transform.translation,
199 rotation_quat_xyzw: local_rotation,
200 scale: transform.scale,
201 },
202 );
203 }
204 }
205
206 IntentValue::GLTFArmatureVisible {
207 component_ids,
208 visible,
209 } => {
210 for &component_id in component_ids {
211 if let Some(gltf) = world
212 .get_component_by_id_as_mut::<crate::engine::ecs::component::GLTFComponent>(
213 component_id,
214 )
215 {
216 gltf.armature_visible = *visible;
217 }
218 }
219 }
220
221 IntentValue::SelectionSet {
222 component_ids,
223 entries,
224 primary,
225 } => {
226 for &selection_root in component_ids.iter() {
227 crate::engine::ecs::system::selection_system::apply_selection_set(
228 world,
229 emit,
230 selection_root,
231 entries.clone(),
232 *primary,
233 );
234 }
235 }
236
237 IntentValue::Attach { parents, child } => {
238 for &parent in parents.iter() {
239 let old_parent = world.parent_of(*child);
240 if let Err(e) = world.add_child(parent, *child) {
241 println!("[IntentExecutor] attach failed: {e}");
242 continue;
243 }
244
245 emit.push_event(
246 *child,
247 EventSignal::ParentChanged {
248 child: *child,
249 old_parent,
250 new_parent: Some(parent),
251 },
252 );
253
254 if world.is_initialized(parent) {
255 world.init_component_tree(*child, emit);
256 }
257
258 emit_topology_transform_refresh(world, emit, *child);
259 emit_topology_transform_refresh(world, emit, parent);
260
261 emit.push_intent_now(
262 parent,
263 IntentValue::AudioGraphDirtyImmediate {
264 component_ids: vec![parent],
265 },
266 );
267 emit.push_intent_now(
268 *child,
269 IntentValue::AudioGraphDirtyImmediate {
270 component_ids: vec![*child],
271 },
272 );
273 }
274 }
275
276 IntentValue::QueryFindComponent {
277 root,
278 selector,
279 reply,
280 } => {
281 let _ = reply.send(world.find_component(*root, selector));
282 }
283
284 IntentValue::QueryFindAllComponents {
285 root,
286 selector,
287 reply,
288 } => {
289 let _ = reply.send(world.find_all_components(*root, selector));
290 }
291
292 IntentValue::AttachClone {
293 parents,
294 prefab_root,
295 } => {
296 let ce_ast = match crate::scripting::component_registry::subtree_to_ce_ast(
299 &*world,
300 *prefab_root,
301 ) {
302 Ok(ce) => ce,
303 Err(e) => {
304 println!("[IntentExecutor] attach_clone encode failed: {e}");
305 return;
306 }
307 };
308 let materialized =
309 match crate::scripting::component_registry::ce_ast_to_materialized(&ce_ast) {
310 Ok(m) => m,
311 Err(e) => {
312 println!("[IntentExecutor] attach_clone materialize failed: {e}");
313 return;
314 }
315 };
316
317 for &parent in parents.iter() {
318 let new_root = match crate::scripting::component_registry::with_live_render_assets(
319 render_assets,
320 || {
321 crate::scripting::component_registry::spawn_tree(
322 &materialized,
323 Some(parent),
324 world,
325 emit,
326 )
327 },
328 ) {
329 Ok(id) => id,
330 Err(e) => {
331 println!("[IntentExecutor] attach_clone spawn failed: {e}");
332 continue;
333 }
334 };
335
336 if world.get_component_record(new_root).is_none() {
337 println!("[IntentExecutor] attach_clone: new root missing after spawn");
338 continue;
339 }
340
341 emit.push_event(
345 new_root,
346 EventSignal::ParentChanged {
347 child: new_root,
348 old_parent: None,
349 new_parent: Some(parent),
350 },
351 );
352
353 emit_topology_transform_refresh(world, emit, new_root);
354 emit_topology_transform_refresh(world, emit, parent);
355
356 emit.push_intent_now(
357 parent,
358 IntentValue::AudioGraphDirtyImmediate {
359 component_ids: vec![parent],
360 },
361 );
362 emit.push_intent_now(
363 new_root,
364 IntentValue::AudioGraphDirtyImmediate {
365 component_ids: vec![new_root],
366 },
367 );
368 }
369 }
370
371 IntentValue::Detach { component_ids } => {
372 for &child in component_ids.iter() {
373 let old_parent = world.parent_of(child);
374 world.detach_from_parent(child);
375
376 emit.push_event(
377 child,
378 EventSignal::ParentChanged {
379 child,
380 old_parent,
381 new_parent: None,
382 },
383 );
384
385 if let Some(p) = old_parent {
386 emit.push_intent_now(
387 p,
388 IntentValue::AudioGraphDirtyImmediate {
389 component_ids: vec![p],
390 },
391 );
392 }
393
394 emit_topology_transform_refresh(world, emit, child);
395 if let Some(p) = old_parent {
396 emit_topology_transform_refresh(world, emit, p);
397 }
398 }
399 }
400
401 IntentValue::RemoveChild { parents, index } => {
402 for &parent in parents.iter() {
403 let child = world.children_of(parent).get(*index).copied();
404 let Some(child) = child else {
405 continue;
406 };
407
408 emit.push_intent_now(
409 parent,
410 IntentValue::AudioGraphDirtyImmediate {
411 component_ids: vec![parent],
412 },
413 );
414 emit.push_intent_now(
415 child,
416 IntentValue::AudioGraphDirtyImmediate {
417 component_ids: vec![child],
418 },
419 );
420
421 world.detach_from_parent(child);
422
423 emit.push_event(
424 child,
425 EventSignal::ParentChanged {
426 child,
427 old_parent: Some(parent),
428 new_parent: None,
429 },
430 );
431
432 emit.push_intent_now(
433 child,
434 IntentValue::RemoveSubtree {
435 component_ids: vec![child],
436 },
437 );
438
439 emit_topology_transform_refresh(world, emit, parent);
440 }
441 }
442
443 IntentValue::RemoveChildren { parents } => {
444 for &parent in parents.iter() {
445 let children: Vec<ComponentId> = world.children_of(parent).to_vec();
446 if children.is_empty() {
447 continue;
448 }
449
450 emit.push_intent_now(
451 parent,
452 IntentValue::AudioGraphDirtyImmediate {
453 component_ids: vec![parent],
454 },
455 );
456 for child in children {
457 emit.push_intent_now(
458 child,
459 IntentValue::AudioGraphDirtyImmediate {
460 component_ids: vec![child],
461 },
462 );
463
464 world.detach_from_parent(child);
465 emit.push_event(
466 child,
467 EventSignal::ParentChanged {
468 child,
469 old_parent: Some(parent),
470 new_parent: None,
471 },
472 );
473
474 emit.push_intent_now(
475 child,
476 IntentValue::RemoveSubtree {
477 component_ids: vec![child],
478 },
479 );
480 }
481
482 emit_topology_transform_refresh(world, emit, parent);
483 }
484 }
485
486 IntentValue::AudioGraphRebuild { component_ids } => {
487 for &t in component_ids.iter() {
488 emit.push_intent_now(
489 t,
490 IntentValue::AudioGraphDirtyImmediate {
491 component_ids: vec![t],
492 },
493 );
494 }
495 }
496
497 IntentValue::RequestRaycast { component_ids } => {
498 let mut raycast_cids = Vec::new();
499 for &t in component_ids.iter() {
500 collect_raycast_targets(world, t, &mut raycast_cids);
501 }
502 raycast_cids.sort();
503 raycast_cids.dedup();
504
505 for rcid in raycast_cids {
506 if let Some(rc) = world.get_component_by_id_as_mut::<RayCastComponent>(rcid) {
507 rc.cast_requests = rc.cast_requests.saturating_add(1);
508 }
509 }
510 }
511
512 IntentValue::AudioLowPassSetCutoffHz {
513 component_ids,
514 cutoff_hz,
515 } => {
516 for &t in component_ids.iter() {
517 if let Some(c) = world.get_component_by_id_as_mut::<AudioLowPassFilterComponent>(t)
518 {
519 c.cutoff_hz = if cutoff_hz.is_finite() {
520 cutoff_hz.max(0.0)
521 } else {
522 c.cutoff_hz
523 };
524 emit.push_intent_now(
525 t,
526 IntentValue::ScheduleAudioOp {
527 component_ids: vec![t],
528 beat: beat_now,
529 op: AudioOp::SetLowPassCutoffHz(c.cutoff_hz),
530 },
531 );
532 }
533 }
534 }
535
536 IntentValue::AudioBandPassSetCenterHz {
537 component_ids,
538 center_hz,
539 } => {
540 for &t in component_ids.iter() {
541 if let Some(c) = world.get_component_by_id_as_mut::<AudioBandPassFilterComponent>(t)
542 {
543 c.center_hz = if center_hz.is_finite() {
544 center_hz.max(0.0)
545 } else {
546 c.center_hz
547 };
548 emit.push_intent_now(
549 t,
550 IntentValue::ScheduleAudioOp {
551 component_ids: vec![t],
552 beat: beat_now,
553 op: AudioOp::SetBandPassCenterHz(c.center_hz),
554 },
555 );
556 }
557 }
558 }
559
560 IntentValue::OscillatorSetEnabled {
561 component_ids,
562 enabled,
563 } => {
564 let mut osc_cids = Vec::new();
565 for &t in component_ids.iter() {
566 collect_oscillator_targets(world, t, &mut osc_cids);
567 }
568 osc_cids.sort();
569 osc_cids.dedup();
570 for osc_cid in osc_cids {
571 if let Some(c) =
572 world.get_component_by_id_as_mut::<AudioOscillatorComponent>(osc_cid)
573 {
574 for osc in c.oscillators.iter_mut() {
575 osc.enabled = *enabled;
576 }
577 emit.push_intent_now(
578 osc_cid,
579 IntentValue::RegisterAudioOscillator {
580 component_ids: vec![osc_cid],
581 },
582 );
583 }
584 }
585 }
586
587 IntentValue::OscillatorSetPitch {
588 component_ids,
589 frequency_hz,
590 } => {
591 if !frequency_hz.is_finite() {
592 println!(
593 "[IntentExecutor] oscillator_set_pitch: non-finite frequency_hz={frequency_hz}"
594 );
595 return;
596 }
597
598 let mut osc_cids = Vec::new();
599 for &t in component_ids.iter() {
600 collect_oscillator_targets(world, t, &mut osc_cids);
601 }
602 osc_cids.sort();
603 osc_cids.dedup();
604 for osc_cid in osc_cids {
605 if let Some(c) =
606 world.get_component_by_id_as_mut::<AudioOscillatorComponent>(osc_cid)
607 {
608 for osc in c.oscillators.iter_mut() {
609 osc.frequency = *frequency_hz;
610 }
611 emit.push_intent_now(
612 osc_cid,
613 IntentValue::RegisterAudioOscillator {
614 component_ids: vec![osc_cid],
615 },
616 );
617 }
618 }
619 }
620
621 IntentValue::OscillatorScheduleSetPitch {
622 component_ids,
623 beat_offset,
624 beat_context,
625 frequency_hz,
626 } => {
627 let beat = beat_context.unwrap_or(beat_now) + *beat_offset;
628
629 let mut osc_cids = Vec::new();
630 for &t in component_ids.iter() {
631 collect_oscillator_targets(world, t, &mut osc_cids);
632 }
633 osc_cids.sort();
634 osc_cids.dedup();
635 for osc_cid in osc_cids {
636 emit.push_intent_now(
637 osc_cid,
638 IntentValue::ScheduleAudioPitchSetHz {
639 component_ids: vec![osc_cid],
640 beat,
641 frequency_hz: *frequency_hz,
642 },
643 );
644 }
645 }
646
647 IntentValue::AudioSchedulePlay {
648 component_ids,
649 beat_offset,
650 beat_context,
651 note,
652 gain,
653 rate: _,
654 duration,
655 } => {
656 let beat = beat_context.unwrap_or(beat_now) + *beat_offset;
657
658 let frequency_hz = note
661 .as_ref()
662 .map(|n| MusicSystem::frequency_hz_for_note(*n));
663
664 let velocity_gain = gain.or_else(|| note.as_ref().map(|n| n.velocity()));
665 let final_gain = velocity_gain.map(|g| if g.is_finite() { g.max(0.0) } else { 1.0 });
666
667 let note_duration = note.as_ref().map(|n| n.duration_beats() as f64);
668 let stop_after = duration.or(note_duration);
669
670 let mut source_cids = Vec::new();
672 for &t in component_ids.iter() {
673 let mut resolved_via_note: Option<ComponentId> = None;
676 if world
677 .get_component_by_id_as::<MusicNoteComponent>(t)
678 .is_some()
679 {
680 let mut taken: MusicNoteComponent = world
681 .get_component_by_id_as::<MusicNoteComponent>(t)
682 .cloned()
683 .unwrap();
684 resolved_via_note = taken.resolve_target(world);
685 if let Some(slot) = world.get_component_by_id_as_mut::<MusicNoteComponent>(t) {
686 slot.target_resolved = taken.target_resolved;
687 }
688 }
689 if let Some(via_note) = resolved_via_note {
690 source_cids.push(via_note);
691 continue;
692 }
693
694 let before = source_cids.len();
695 collect_audio_source_targets(world, t, &mut source_cids);
696 if let Some(&found) = source_cids[before..].first() {
699 if let Some(mn) = world.get_component_by_id_as_mut::<MusicNoteComponent>(t) {
700 if mn.target_resolved.is_none() {
701 mn.target_resolved = Some(found);
702 }
703 }
704 }
705 }
706 source_cids.sort();
707 source_cids.dedup();
708
709 for src_cid in source_cids {
710 if world
713 .get_component_by_id_as::<AudioOscillatorComponent>(src_cid)
714 .is_some()
715 {
716 emit.push_intent_now(
717 src_cid,
718 IntentValue::ScheduleAudioOscillatorEnabled {
719 component_ids: vec![src_cid],
720 beat,
721 enabled: true,
722 },
723 );
724 if let Some(frequency_hz) = frequency_hz {
725 emit.push_intent_now(
726 src_cid,
727 IntentValue::ScheduleAudioPitchSetHz {
728 component_ids: vec![src_cid],
729 beat,
730 frequency_hz,
731 },
732 );
733 }
734 if let Some(g) = final_gain {
735 emit.push_intent_now(
736 src_cid,
737 IntentValue::ScheduleAudioGainSet {
738 component_ids: vec![src_cid],
739 beat,
740 gain: g,
741 },
742 );
743 }
744
745 if let Some(dur) = stop_after {
746 if dur.is_finite() && dur > 0.0 {
747 emit.push_intent_now(
748 src_cid,
749 IntentValue::ScheduleAudioOscillatorEnabled {
750 component_ids: vec![src_cid],
751 beat: beat + dur,
752 enabled: false,
753 },
754 );
755 if final_gain.is_some() {
756 emit.push_intent_now(
757 src_cid,
758 IntentValue::ScheduleAudioGainSet {
759 component_ids: vec![src_cid],
760 beat: beat + dur,
761 gain: 1.0,
762 },
763 );
764 }
765 }
766 }
767 } else if let Some(clip) =
768 world.get_component_by_id_as::<AudioClipComponent>(src_cid)
769 {
770 match &clip.load_state {
771 AudioClipLoadState::Failed(reason) => {
772 eprintln!(
773 "[AudioSchedulePlay] skip clip {:?} ({}): {}",
774 src_cid, clip.uri, reason
775 );
776 }
777 AudioClipLoadState::Pending | AudioClipLoadState::Loaded => {
778 emit.push_intent_now(
783 src_cid,
784 IntentValue::ScheduleAudioOscillatorEnabled {
785 component_ids: vec![src_cid],
786 beat,
787 enabled: true,
788 },
789 );
790 if let Some(g) = final_gain {
791 emit.push_intent_now(
792 src_cid,
793 IntentValue::ScheduleAudioGainSet {
794 component_ids: vec![src_cid],
795 beat,
796 gain: g,
797 },
798 );
799 }
800 if let Some(dur) = stop_after {
801 if dur.is_finite() && dur > 0.0 {
802 emit.push_intent_now(
803 src_cid,
804 IntentValue::ScheduleAudioOscillatorEnabled {
805 component_ids: vec![src_cid],
806 beat: beat + dur,
807 enabled: false,
808 },
809 );
810 }
811 }
812 }
813 }
814 }
815 }
816 }
817
818 _ => {}
819 }
820}
821
822fn collect_raycast_targets(world: &World, target: ComponentId, out: &mut Vec<ComponentId>) {
823 use crate::engine::ecs::component::RayCastComponent;
824
825 if world
826 .get_component_by_id_as::<RayCastComponent>(target)
827 .is_some()
828 {
829 out.push(target);
830 return;
831 }
832
833 let mut stack = vec![target];
835 while let Some(node) = stack.pop() {
836 if world
837 .get_component_by_id_as::<RayCastComponent>(node)
838 .is_some()
839 {
840 out.push(node);
841 continue;
842 }
843 for &ch in world.children_of(node).iter() {
844 stack.push(ch);
845 }
846 }
847}
848
849fn collect_transform_targets(world: &World, target: ComponentId, out: &mut Vec<ComponentId>) {
850 use crate::engine::ecs::component::TransformComponent;
851
852 if world
853 .get_component_by_id_as::<TransformComponent>(target)
854 .is_some()
855 {
856 out.push(target);
857 return;
858 }
859
860 let mut stack = vec![target];
862 while let Some(node) = stack.pop() {
863 if world
864 .get_component_by_id_as::<TransformComponent>(node)
865 .is_some()
866 {
867 out.push(node);
868 continue;
869 }
870 for &ch in world.children_of(node).iter() {
871 stack.push(ch);
872 }
873 }
874}
875
876fn emit_topology_transform_refresh(world: &World, emit: &mut dyn SignalEmitter, cid: ComponentId) {
877 use crate::engine::ecs::IntentValue;
878 use crate::engine::ecs::component::{TransformComponent, TransformParentComponent};
879
880 if let Some(t) = world.get_component_by_id_as::<TransformComponent>(cid) {
883 let _ = t;
884 emit.push_intent_now(
885 cid,
886 IntentValue::UpdateTransformWorld {
887 component_ids: vec![cid],
888 },
889 );
890 return;
891 }
892
893 if world
894 .get_component_by_id_as::<TransformParentComponent>(cid)
895 .is_some()
896 {
897 emit.push_intent_now(
898 cid,
899 IntentValue::UpdateTransformWorld {
900 component_ids: vec![cid],
901 },
902 );
903 return;
904 }
905
906 let mut cur = cid;
908 while let Some(p) = world.parent_of(cur) {
909 if world
910 .get_component_by_id_as::<TransformParentComponent>(p)
911 .is_some()
912 {
913 emit.push_intent_now(
914 p,
915 IntentValue::UpdateTransformWorld {
916 component_ids: vec![p],
917 },
918 );
919 return;
920 }
921 if let Some(t) = world.get_component_by_id_as::<TransformComponent>(p) {
922 let _ = t;
923 emit.push_intent_now(
924 p,
925 IntentValue::UpdateTransformWorld {
926 component_ids: vec![p],
927 },
928 );
929 return;
930 }
931 cur = p;
932 }
933}
934
935fn collect_color_targets(world: &World, target: ComponentId, out: &mut Vec<ComponentId>) {
936 use crate::engine::ecs::component::{ColorComponent, RenderableComponent};
937
938 if world
940 .get_component_by_id_as::<ColorComponent>(target)
941 .is_some()
942 {
943 out.push(target);
944 return;
945 }
946
947 if world
949 .get_component_by_id_as::<RenderableComponent>(target)
950 .is_some()
951 {
952 for &ch in world.children_of(target) {
953 if world.get_component_by_id_as::<ColorComponent>(ch).is_some() {
954 out.push(ch);
955 return;
956 }
957 }
958 return;
959 }
960
961 let mut stack = vec![target];
963 while let Some(node) = stack.pop() {
964 for &ch in world.children_of(node) {
965 stack.push(ch);
966 }
967
968 if world
969 .get_component_by_id_as::<RenderableComponent>(node)
970 .is_some()
971 {
972 for &ch in world.children_of(node) {
973 if world.get_component_by_id_as::<ColorComponent>(ch).is_some() {
974 out.push(ch);
975 }
976 }
977 }
978 }
979}
980
981fn is_audio_source(world: &World, id: ComponentId) -> bool {
982 use crate::engine::ecs::component::{AudioClipComponent, AudioOscillatorComponent};
983 world
984 .get_component_by_id_as::<AudioOscillatorComponent>(id)
985 .is_some()
986 || world
987 .get_component_by_id_as::<AudioClipComponent>(id)
988 .is_some()
989}
990
991fn collect_audio_source_targets(world: &World, target: ComponentId, out: &mut Vec<ComponentId>) {
998 if is_audio_source(world, target) {
999 out.push(target);
1000 return;
1001 }
1002
1003 let before = out.len();
1004 let mut stack = vec![target];
1005 while let Some(node) = stack.pop() {
1006 for &ch in world.children_of(node) {
1007 stack.push(ch);
1008 }
1009 if is_audio_source(world, node) {
1010 out.push(node);
1011 }
1012 }
1013
1014 if out.len() == before {
1015 let mut cur = target;
1016 while let Some(p) = world.parent_of(cur) {
1017 if is_audio_source(world, p) {
1018 out.push(p);
1019 return;
1020 }
1021 cur = p;
1022 }
1023 }
1024}
1025
1026fn collect_oscillator_targets(world: &World, target: ComponentId, out: &mut Vec<ComponentId>) {
1027 use crate::engine::ecs::component::AudioOscillatorComponent;
1028
1029 if world
1030 .get_component_by_id_as::<AudioOscillatorComponent>(target)
1031 .is_some()
1032 {
1033 out.push(target);
1034 return;
1035 }
1036
1037 let before = out.len();
1038 let mut stack = vec![target];
1039 while let Some(node) = stack.pop() {
1040 for &ch in world.children_of(node) {
1041 stack.push(ch);
1042 }
1043
1044 if world
1045 .get_component_by_id_as::<AudioOscillatorComponent>(node)
1046 .is_some()
1047 {
1048 out.push(node);
1049 }
1050 }
1051
1052 if out.len() == before {
1053 let mut cur = target;
1054 while let Some(p) = world.parent_of(cur) {
1055 if world
1056 .get_component_by_id_as::<AudioOscillatorComponent>(p)
1057 .is_some()
1058 {
1059 out.push(p);
1060 return;
1061 }
1062 cur = p;
1063 }
1064 }
1065}