Skip to main content

ActionComponent

Struct ActionComponent 

Source
pub struct ActionComponent {
    pub signal: IntentValue,
    pub target_sources: Vec<ComponentRef>,
    pub resolved: bool,
}

Fields§

§signal: IntentValue

Runtime intent. ComponentId slots inside are ComponentId::null() placeholders until resolution; after resolution, they’re real ids filled in from target_sources in the variant’s declaration order.

§target_sources: Vec<ComponentRef>

Authoring metadata, one entry per ComponentId slot in signal, ordered by the slot’s declaration order in the variant. Used by dump (lossless round-trip) and by resolution (look up ids).

§resolved: bool

Whether signal’s ComponentId slots hold real ids (true) or null placeholders (false). Set by the resolution pass invoked by AnimationSystem per the owning AnimationComponent’s configured resolve timing.

Implementations§

Source§

impl ActionComponent

Source

pub fn new(signal: IntentValue) -> Self

Construct from an already-resolved IntentValue (no ComponentId targets, or all targets pre-resolved). Use this for built-in / engine-emitted actions; MMS authoring goes through the registry which builds with new_authored instead.

Examples found in repository?
examples/mesh-factory-example.rs (lines 94-101)
55    fn spawn_labeled_mesh(
56        universe: &mut engine::Universe,
57        x: f32,
58        y: f32,
59        label: &str,
60        mesh: engine::graphics::primitives::CpuMeshHandle,
61        scale: [f32; 3],
62        color: [f32; 4],
63    ) {
64        use engine::ecs::component::{
65            ActionComponent, AnimationComponent, AnimationState, ColorComponent, EmissiveComponent,
66            KeyframeComponent, RenderableComponent, TextComponent, TransformComponent,
67        };
68        use engine::graphics::primitives::{MaterialHandle, Renderable};
69
70        // Mesh.
71        let root = universe.world.add_component(
72            TransformComponent::new()
73                .with_position(x, y, 0.0)
74                .with_scale(scale[0], scale[1], scale[2]),
75        );
76
77        // Spin each shape around its own +Y axis using AnimationComponent + keyframes.
78        // We fill [0, 2) beats densely so it looks smooth.
79        let anim = universe
80            .world
81            .add_component(AnimationComponent::new().with_state(AnimationState::Looping));
82        let _ = universe.attach(root, anim);
83
84        let steps: usize = 64;
85        for i in 0..steps {
86            let beat = (i as f64) * (2.0 / (steps as f64));
87            let kf = universe.world.add_component(KeyframeComponent::new(beat));
88            let _ = universe.attach(anim, kf);
89
90            // Full turn over 2 beats.
91            let angle = (std::f64::consts::TAU * (beat / 2.0)) as f32;
92            let rotation = utils::math::quat_from_axis_angle([0.0, 1.0, 0.0], angle);
93
94            let action_cid = universe.world.add_component(ActionComponent::new(
95                engine::ecs::IntentValue::UpdateTransform {
96                    component_ids: vec![root],
97                    translation: [x, y, 0.0],
98                    rotation_quat_xyzw: rotation,
99                    scale,
100                },
101            ));
102            let _ = universe.attach(kf, action_cid);
103        }
104
105        let renderable = universe
106            .world
107            .add_component(RenderableComponent::new(Renderable::new(
108                mesh,
109                MaterialHandle::TOON_MESH,
110            )));
111        let color_c = universe
112            .world
113            .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
114        let emissive = universe.world.add_component(EmissiveComponent::on());
115
116        let _ = universe.attach(root, renderable);
117        let _ = universe.attach(renderable, color_c);
118        let _ = universe.attach(renderable, emissive);
119
120        universe.add(root);
121
122        // Label (separate transform so we can scale text independently).
123        let text_root = universe.world.add_component(
124            TransformComponent::new()
125                .with_position(x, y + 0.75, 0.05)
126                .with_scale(0.09, 0.09, 1.0),
127        );
128        let text = universe
129            .world
130            .add_component(TextComponent::with_word_wrap_tokens(
131                label,
132                LABEL_WRAP_AT,
133                ["::", "(", ")", ",", "."],
134            ));
135        let text_color = universe
136            .world
137            .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
138        let text_emissive = universe.world.add_component(EmissiveComponent::on());
139        let _ = universe.attach(text_root, text);
140        let _ = universe.attach(text, text_color);
141        let _ = universe.attach(text, text_emissive);
142        universe.add(text_root);
143    }
More examples
Hide additional examples
examples/text-animation.rs (lines 114-119)
6fn main() {
7    mittens_engine::example_support::ensure_model_assets();
8    utils::logger::init();
9
10    let world = engine::ecs::World::default();
11    let mut universe = engine::Universe::new(world);
12
13    // Input-driven camera rig.
14    // Topology: I { T { C3D } }
15    let input = universe
16        .world
17        .add_component(engine::ecs::component::InputComponent::new().with_speed(1.5));
18    let camera3d = universe
19        .world
20        .add_component(engine::ecs::component::Camera3DComponent::new());
21    let rig_transform = universe.world.add_component(
22        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 2.5),
23    );
24    let input_mode = universe.world.add_component(
25        engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
26    );
27    let _ = universe.attach(input, input_mode);
28    let _ = universe.attach(input, rig_transform);
29    let _ = universe.attach(rig_transform, camera3d);
30
31    // Small on-screen help.
32    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
33    universe.add(input);
34
35    // Light so we can see non-emissive materials.
36    let light_transform = universe.world.add_component(
37        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 2.0),
38    );
39    let light = universe.world.add_component(
40        engine::ecs::component::PointLightComponent::new()
41            .with_distance(25.0)
42            .with_color(1.0, 1.0, 1.0),
43    );
44    let _ = universe.attach(light_transform, light);
45    universe.add(light_transform);
46
47    use engine::ecs::component::{
48        ColorComponent, TextComponent, TextShadowComponent, TextureComponent,
49        TextureFilteringComponent, TransformComponent, TransparentCutoutComponent,
50    };
51
52    // Styled text root.
53    let text_root = universe.world.add_component(
54        TransformComponent::new()
55            .with_position(0.0, 0.0, 0.0)
56            .with_scale(0.25, 0.25, 1.0),
57    );
58
59    // Color must be an ancestor of the glyph renderables.
60    let color_id = universe
61        .world
62        .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
63    let _ = universe.attach(text_root, color_id);
64
65    // This is the component we animate via SetText.
66    let text_id = universe.world.add_component(TextComponent::new(">w<"));
67    let _ = universe.attach(color_id, text_id);
68
69    // Route into cutout pass for cleaner edges.
70    let cutout = universe
71        .world
72        .add_component(TransparentCutoutComponent::new());
73    let _ = universe.attach(text_id, cutout);
74
75    // Font atlas.
76    let tex = universe.world.add_component(TextureComponent::with_uri(
77        "assets/textures/font_system.dds",
78    ));
79    let _ = universe.attach(text_id, tex);
80
81    let shadow = universe.world.add_component(
82        TextShadowComponent::new()
83            .with_scale(1.35)
84            .with_offset([0.06, -0.06, 0.0015]),
85    );
86    let filtering = universe
87        .world
88        .add_component(TextureFilteringComponent::nearest_magnification());
89
90    let _ = universe.attach(text_id, shadow);
91    let _ = universe.attach(text_id, filtering);
92
93    universe.add(text_root);
94
95    let clock_component = universe
96        .world
97        .add_component(engine::ecs::component::ClockComponent::new().with_bpm(140.0));
98    let _ = universe.add(clock_component);
99
100    // Looping animation: 4 beats long, 4 keyframes.
101    let anim = universe
102        .world
103        .add_component(engine::ecs::component::AnimationComponent::new());
104
105    let faces = [">w<", "^w^", "-_-", "o_o"];
106    for (i, &face) in faces.iter().enumerate() {
107        let kf = universe
108            .world
109            .add_component(engine::ecs::component::KeyframeComponent::new(i as f64));
110
111        // Each keyframe emits a SetText intent.
112        let action = universe
113            .world
114            .add_component(engine::ecs::component::ActionComponent::new(
115                engine::ecs::IntentValue::SetText {
116                    component_ids: vec![text_id],
117                    text: face.to_string(),
118                },
119            ));
120
121        let _ = universe.attach(anim, kf);
122        let _ = universe.attach(kf, action);
123    }
124
125    universe.add(anim);
126
127    // Process init-time registrations (Text expands into glyph subtrees here).
128    universe.systems.process_commands(
129        &mut universe.world,
130        &mut universe.visuals,
131        &mut universe.render_assets,
132        &mut universe.command_queue,
133    );
134
135    engine::Windowing::run_app(universe).expect("Windowing failed");
136}
examples/raycast-topology-animation.rs (lines 299-304)
111fn main() {
112    mittens_engine::example_support::ensure_model_assets();
113    utils::logger::init();
114
115    let world = engine::ecs::World::default();
116    let mut universe = engine::Universe::new(world);
117
118    // Background.
119    let bg_color = universe
120        .world
121        .add_component(engine::ecs::component::BackgroundColorComponent::new());
122    let bg_color_c = universe
123        .world
124        .add_component(engine::ecs::component::ColorComponent::rgba(
125            0.1, 0.1, 0.1, 1.0,
126        ));
127    let _ = universe.world.add_child(bg_color, bg_color_c);
128    universe.add(bg_color);
129
130    // Camera rig so we can see the scene.
131    let input = universe
132        .world
133        .add_component(engine::ecs::component::InputComponent::new().with_speed(2.0));
134
135    let rig_transform = universe.world.add_component(
136        engine::ecs::component::TransformComponent::new()
137            .with_position(0.0, 2.0, 8.0)
138            .with_rotation_euler(-0.25, 0.0, 0.0),
139    );
140
141    let camera3d = universe
142        .world
143        .add_component(engine::ecs::component::Camera3DComponent::new());
144
145    let input_mode = universe.world.add_component(
146        engine::ecs::component::InputTransformModeComponent::forward_z()
147            .with_roll_axis_y()
148            .with_fps_rotation(),
149    );
150
151    let _ = universe.attach(input, input_mode);
152    let _ = universe.attach(input, rig_transform);
153    let _ = universe.attach(rig_transform, camera3d);
154
155    // Topology: I { T { C3D } } — add a small camera-attached controls hint.
156    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
157    universe.add(input);
158
159    // Two rotating anchor transforms that the raycaster will be reparented under.
160    // Each ring has its own anchor at a different height.
161    let anchor_a = universe.world.add_component(
162        engine::ecs::component::TransformComponent::new().with_position(0.0, 1.0, 0.0),
163    );
164    let anchor_b = universe.world.add_component(
165        engine::ecs::component::TransformComponent::new().with_position(0.0, 2.2, 0.0),
166    );
167
168    // Visual markers for A/B.
169    fn marker(universe: &mut engine::Universe, parent: engine::ecs::ComponentId, rgba: [f32; 4]) {
170        let r = universe
171            .world
172            .add_component(engine::ecs::component::RenderableComponent::cube());
173        let rc = universe
174            .world
175            .add_component(engine::ecs::component::RaycastableComponent::disabled());
176        let c = universe
177            .world
178            .add_component(engine::ecs::component::ColorComponent::rgba(
179                rgba[0], rgba[1], rgba[2], rgba[3],
180            ));
181        let e = universe
182            .world
183            .add_component(engine::ecs::component::EmissiveComponent::on());
184        let t = universe.world.add_component(
185            engine::ecs::component::TransformComponent::new().with_scale(0.15, 0.15, 0.15),
186        );
187        let _ = universe.attach(parent, t);
188        let _ = universe.attach(t, r);
189        let _ = universe.attach(r, rc);
190        let _ = universe.attach(r, c);
191        let _ = universe.attach(r, e);
192    }
193
194    marker(&mut universe, anchor_a, [0.9, 0.2, 0.2, 1.0]);
195    marker(&mut universe, anchor_b, [0.2, 0.2, 0.9, 1.0]);
196
197    universe.add(anchor_a);
198    universe.add(anchor_b);
199
200    // A ring of cubes around the origin to see which one gets hit.
201    fn ring_cube(
202        universe: &mut engine::Universe,
203        ring_root: engine::ecs::ComponentId,
204        x: f32,
205        y: f32,
206        z: f32,
207        rgba: [f32; 4],
208    ) {
209        let t = universe.world.add_component(
210            engine::ecs::component::TransformComponent::new()
211                .with_position(x, y, z)
212                .with_scale(0.35, 0.35, 0.35),
213        );
214        let r = universe
215            .world
216            .add_component(engine::ecs::component::RenderableComponent::cube());
217        let rc = universe
218            .world
219            .add_component(engine::ecs::component::RaycastableComponent::enabled());
220        let c = universe
221            .world
222            .add_component(engine::ecs::component::ColorComponent::rgba(
223                rgba[0], rgba[1], rgba[2], rgba[3],
224            ));
225        let _ = universe.attach(ring_root, t);
226        let _ = universe.attach(t, r);
227        let _ = universe.attach(r, rc);
228        let _ = universe.attach(r, c);
229    }
230
231    // Two rings: one per anchor height.
232    let n = 28;
233    let (radius_a, y_a) = (4.0, 1.0);
234    let (radius_b, y_b) = (2.6, 2.2);
235
236    let ring_a_root = universe
237        .world
238        .add_component(engine::ecs::component::TransformComponent::new());
239    let ring_b_root = universe
240        .world
241        .add_component(engine::ecs::component::TransformComponent::new());
242
243    for i in 0..n {
244        let a = (i as f32) * (std::f32::consts::TAU / (n as f32));
245        let (x, z) = (radius_a * a.cos(), radius_a * a.sin());
246        let color = if i % 2 == 0 {
247            [0.55, 0.20, 0.20, 1.0]
248        } else {
249            [0.20, 0.55, 0.20, 1.0]
250        };
251        ring_cube(&mut universe, ring_a_root, x, y_a, z, color);
252    }
253
254    for i in 0..n {
255        let a = (i as f32) * (std::f32::consts::TAU / (n as f32));
256        let (x, z) = (radius_b * a.cos(), radius_b * a.sin());
257        let color = if i % 2 == 0 {
258            [0.20, 0.25, 0.60, 1.0]
259        } else {
260            [0.20, 0.55, 0.55, 1.0]
261        };
262        ring_cube(&mut universe, ring_b_root, x, y_b, z, color);
263    }
264
265    // Init rings and attach scoped interaction handlers.
266    universe.add(ring_a_root);
267    universe.add(ring_b_root);
268    universe.add_signal_handler(
269        engine::ecs::SignalKind::RayIntersected,
270        ring_a_root,
271        ring_a_handler,
272    );
273    universe.add_signal_handler(
274        engine::ecs::SignalKind::RayIntersected,
275        ring_b_root,
276        ring_b_handler,
277    );
278
279    // The raycaster component we will move between parents.
280    // Source is inferred from topology:
281    // - Under transforms A/B (no camera child) => parent-forward (-Z)
282    // - Under camera rig transform (has camera child) => cursor-through-camera
283    let raycaster = universe.world.add_component(
284        engine::ecs::component::RayCastComponent::event_driven().with_max_distance(25.0),
285    );
286
287    // Global animation: move the raycaster between anchors.
288    // Loop length is 8 beats (we include a noop keyframe at beat 7.0 to force loop_len=8).
289    let anim_global = universe
290        .world
291        .add_component(engine::ecs::component::AnimationComponent::new());
292    {
293        // beat 0: attach to A.
294        let kf0 = universe
295            .world
296            .add_component(engine::ecs::component::KeyframeComponent::new(0.0));
297        let act0 = universe
298            .world
299            .add_component(engine::ecs::component::ActionComponent::new(
300                engine::ecs::IntentValue::Attach {
301                    parents: vec![anchor_a],
302                    child: raycaster,
303                },
304            ));
305        let _ = universe.attach(kf0, act0);
306        let _ = universe.attach(anim_global, kf0);
307
308        // beat 4: attach to B.
309        let kf4 = universe
310            .world
311            .add_component(engine::ecs::component::KeyframeComponent::new(4.0));
312        let act4 = universe
313            .world
314            .add_component(engine::ecs::component::ActionComponent::new(
315                engine::ecs::IntentValue::Attach {
316                    parents: vec![anchor_b],
317                    child: raycaster,
318                },
319            ));
320        let _ = universe.attach(kf4, act4);
321        let _ = universe.attach(anim_global, kf4);
322
323        // beat 7: noop to make loop_len=8.
324        let kf7 = universe
325            .world
326            .add_component(engine::ecs::component::KeyframeComponent::new(7.0));
327        let noop = universe
328            .world
329            .add_component(engine::ecs::component::ActionComponent::new(
330                engine::ecs::IntentValue::Noop,
331            ));
332        let _ = universe.attach(kf7, noop);
333        let _ = universe.attach(anim_global, kf7);
334    }
335    universe.add(anim_global);
336
337    // Ring A animation: rotate anchor A around Y (1 rev / 8 beats).
338    // Also triggers Action::raycast(raycaster) on downbeats in the first half: beats 0,1,2,3.
339    let anim_a = universe
340        .world
341        .add_component(engine::ecs::component::AnimationComponent::new());
342
343    // Ring B animation: rotate anchor B differently (yaw + pitch).
344    // Also triggers Action::raycast(raycaster) on offbeats in the second half: beats 4.5,5.5,6.5,7.5.
345    let anim_b = universe
346        .world
347        .add_component(engine::ecs::component::AnimationComponent::new());
348
349    let loop_beats = 8.0;
350    let steps = 64;
351
352    for step in 0..=steps {
353        let t = (step as f32) / (steps as f32);
354        let beat = (t as f64) * (loop_beats as f64);
355
356        // Keyframe beats are per-animation local beats.
357        let kf_a = universe
358            .world
359            .add_component(engine::ecs::component::KeyframeComponent::new(beat));
360        let kf_b = universe
361            .world
362            .add_component(engine::ecs::component::KeyframeComponent::new(beat));
363
364        // Anchor A rotation: smooth yaw.
365        let yaw_a = t * std::f32::consts::TAU;
366        let a_set = engine::ecs::component::ActionComponent::new(
367            engine::ecs::IntentValue::UpdateTransform {
368                component_ids: vec![anchor_a],
369                translation: [0.0, 1.0, 0.0],
370                rotation_quat_xyzw: quat_from_yaw(yaw_a),
371                scale: [1.0, 1.0, 1.0],
372            },
373        );
374        let a_set_id = universe.world.add_component(a_set);
375        let _ = universe.attach(kf_a, a_set_id);
376
377        // Anchor B rotation: yaw + pitch (different pattern).
378        let yaw_b = -t * std::f32::consts::TAU * 1.5;
379        let pitch_b = (t * std::f32::consts::TAU).sin() * 0.35;
380        let rot_b = quat_mul(quat_from_yaw(yaw_b), quat_from_pitch(pitch_b));
381        let b_set = engine::ecs::component::ActionComponent::new(
382            engine::ecs::IntentValue::UpdateTransform {
383                component_ids: vec![anchor_b],
384                translation: [0.0, 2.2, 0.0],
385                rotation_quat_xyzw: rot_b,
386                scale: [1.0, 1.0, 1.0],
387            },
388        );
389        let b_set_id = universe.world.add_component(b_set);
390        let _ = universe.attach(kf_b, b_set_id);
391
392        // Per-ring raycast patterns.
393        // A: downbeats in first half (0..4): step % 8 == 0 -> beats 0,1,2,3,4.
394        if beat < 4.0 && step % 8 == 0 {
395            let act = universe
396                .world
397                .add_component(engine::ecs::component::ActionComponent::new(
398                    engine::ecs::IntentValue::RequestRaycast {
399                        component_ids: vec![raycaster],
400                    },
401                ));
402            let _ = universe.attach(kf_a, act);
403        }
404
405        // B: offbeats in second half (4..8): step % 8 == 4 -> beats 0.5,1.5,...,7.5.
406        if beat >= 4.0 && step % 8 == 4 {
407            let act = universe
408                .world
409                .add_component(engine::ecs::component::ActionComponent::new(
410                    engine::ecs::IntentValue::RequestRaycast {
411                        component_ids: vec![raycaster],
412                    },
413                ));
414            let _ = universe.attach(kf_b, act);
415        }
416
417        let _ = universe.attach(anim_a, kf_a);
418        let _ = universe.attach(anim_b, kf_b);
419    }
420
421    universe.add(anim_a);
422    universe.add(anim_b);
423
424    // Process init-time registrations.
425    universe.systems.process_commands(
426        &mut universe.world,
427        &mut universe.visuals,
428        &mut universe.render_assets,
429        &mut universe.command_queue,
430    );
431
432    engine::Windowing::run_app(universe).expect("Windowing failed");
433}
examples/animation-for-topology.rs (lines 213-217)
80fn main() {
81    mittens_engine::example_support::ensure_model_assets();
82    utils::logger::init();
83
84    let world = engine::ecs::World::default();
85    let mut universe = engine::Universe::new(world);
86
87    // Minimal scene with a camera so the window opens.
88    let clear = universe
89        .world
90        .add_component(engine::ecs::component::BackgroundColorComponent::new());
91    let clear_c = universe
92        .world
93        .add_component(engine::ecs::component::ColorComponent::rgba(
94            0.06, 0.06, 0.07, 1.0,
95        ));
96    let _ = universe.world.add_child(clear, clear_c);
97    universe.add(clear);
98
99    // Input-driven camera rig.
100    let input = universe
101        .world
102        .add_component(engine::ecs::component::InputComponent::new().with_speed(2.0));
103    let rig_transform = universe.world.add_component(
104        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 9.0),
105    );
106    let input_mode = universe.world.add_component(
107        engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
108    );
109    let camera3d = universe.world.add_component(
110        engine::ecs::component::Camera3DComponent::new()
111            .with_far(250.0)
112            .with_fov(70.0),
113    );
114    let _ = universe.attach(input, input_mode);
115    let _ = universe.attach(input, rig_transform);
116    let _ = universe.attach(rig_transform, camera3d);
117
118    // Topology: I { T { C3D } } — add a small camera-attached controls hint.
119    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
120    universe.add(input);
121
122    // Light so we can see non-emissive materials.
123    let light_tx = universe.world.add_component(
124        engine::ecs::component::TransformComponent::new().with_position(2.0, 3.5, 6.0),
125    );
126    let light = universe.world.add_component(
127        engine::ecs::component::PointLightComponent::new()
128            .with_distance(30.0)
129            .with_color(1.0, 1.0, 1.0),
130    );
131    let _ = universe.attach(light_tx, light);
132    universe.add(light_tx);
133
134    // ClockComponent drives the animation timeline in beats.
135    let clock = universe
136        .world
137        .add_component(engine::ecs::component::ClockComponent::new().with_bpm(140.0));
138    universe.add(clock);
139
140    // Root for all visualization objects.
141    let viz_root = universe.world.add_component(
142        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
143    );
144    universe.add(viz_root);
145
146    let anchor_count = 16usize;
147    let cube_pool_size = 8usize;
148
149    // Three grids side-by-side.
150    let layout_a = GridLayout {
151        origin: (-6.0, 0.0, 0.0),
152        spacing: 1.1,
153    };
154    let layout_b = GridLayout {
155        origin: (0.0, 0.0, 0.0),
156        spacing: 1.1,
157    };
158    let layout_c = GridLayout {
159        origin: (6.0, 0.0, 0.0),
160        spacing: 1.1,
161    };
162
163    // --- Grid A: detach + re-attach (reparent) ---
164    let grid_a_root = universe
165        .world
166        .add_component(engine::ecs::component::TransformComponent::new());
167    let _ = universe.attach(viz_root, grid_a_root);
168
169    let mut anchors_a: Vec<engine::ecs::ComponentId> = Vec::with_capacity(anchor_count);
170    for i in 0..anchor_count {
171        let (x, y, z) = grid_anchor_local(layout_a, i);
172        let anchor = universe.world.add_component(
173            engine::ecs::component::TransformComponent::new().with_position(x, y, z),
174        );
175        let _ = universe.attach(grid_a_root, anchor);
176        anchors_a.push(anchor);
177        spawn_emissive_marker_cube(
178            &mut universe,
179            anchor,
180            (0.0, -0.35, 0.0),
181            0.06,
182            [0.18, 0.18, 0.22, 1.0],
183        );
184    }
185
186    let mut cubes_a: Vec<engine::ecs::ComponentId> = Vec::with_capacity(cube_pool_size);
187    for i in 0..cube_pool_size {
188        let t = (i as f32) / ((cube_pool_size - 1) as f32).max(1.0);
189        let rgba = [0.10, 0.40 + 0.50 * t, 0.90 - 0.70 * t, 1.0];
190        cubes_a.push(spawn_detached_cube_prefab(&mut universe, 0.22, rgba));
191    }
192
193    let anim_a = universe
194        .world
195        .add_component(engine::ecs::component::AnimationComponent::new());
196    for i in 0..anchor_count {
197        let cube = cubes_a[i % cube_pool_size];
198        let parent = anchors_a[i];
199
200        // Explicit detach+attach to test topology changes via animations.
201
202        // Put them on separate keyframes so ordering is time-deterministic.
203        let beat_detach = i as f64;
204        let beat_attach = i as f64 + 0.05;
205
206        let kf_detach = universe
207            .world
208            .add_component(engine::ecs::component::KeyframeComponent::new(beat_detach));
209        let _ = universe.attach(anim_a, kf_detach);
210        let detach_action =
211            universe
212                .world
213                .add_component(engine::ecs::component::ActionComponent::new(
214                    engine::ecs::IntentValue::Detach {
215                        component_ids: vec![cube],
216                    },
217                ));
218        let _ = universe.attach(kf_detach, detach_action);
219
220        let kf_attach = universe
221            .world
222            .add_component(engine::ecs::component::KeyframeComponent::new(beat_attach));
223        let _ = universe.attach(anim_a, kf_attach);
224        let attach_action =
225            universe
226                .world
227                .add_component(engine::ecs::component::ActionComponent::new(
228                    engine::ecs::IntentValue::Attach {
229                        parents: vec![parent],
230                        child: cube,
231                    },
232                ));
233        let _ = universe.attach(kf_attach, attach_action);
234    }
235    universe.add(anim_a);
236
237    // --- Grid B: continuously spawn (attach_clone) + delete-behind (remove_child) ---
238    //
239    // Uses the new actions:
240    // - `Action::attach_clone(parent, prefab_root)`
241    // - `Action::remove_child(parent, index)`
242    //
243    // This avoids needing a pre-built pool of cube ComponentIds.
244    let grid_b_root = universe
245        .world
246        .add_component(engine::ecs::component::TransformComponent::new());
247    let _ = universe.attach(viz_root, grid_b_root);
248
249    let mut anchors_b: Vec<engine::ecs::ComponentId> = Vec::with_capacity(anchor_count);
250    for i in 0..anchor_count {
251        let (x, y, z) = grid_anchor_local(layout_b, i);
252        let anchor = universe.world.add_component(
253            engine::ecs::component::TransformComponent::new().with_position(x, y, z),
254        );
255        let _ = universe.attach(grid_b_root, anchor);
256        anchors_b.push(anchor);
257
258        // Marker is attached under the grid root (not under the anchor), so anchor child index 0
259        // remains reserved for the dynamic cube subtree.
260        spawn_emissive_marker_cube(
261            &mut universe,
262            grid_b_root,
263            (x, y - 0.35, z),
264            0.06,
265            [0.22, 0.18, 0.18, 1.0],
266        );
267    }
268
269    // Detached prefab that will be cloned on demand (reddish cube).
270    let prefab_b = spawn_detached_cube_prefab(&mut universe, 0.20, [0.90, 0.25, 0.15, 1.0]);
271
272    let steps_b = 32usize;
273    let window = 8usize;
274
275    let anim_b = universe
276        .world
277        .add_component(engine::ecs::component::AnimationComponent::new());
278
279    // Phase 1: each beat, spawn a cube under an anchor; delete-behind by removing child(0).
280    for i in 0..steps_b {
281        let parent = anchors_b[i % anchor_count];
282
283        let beat_attach = i as f64;
284        let beat_remove = i as f64 + 0.05;
285
286        let kf_attach = universe
287            .world
288            .add_component(engine::ecs::component::KeyframeComponent::new(beat_attach));
289        let _ = universe.attach(anim_b, kf_attach);
290
291        let attach_action =
292            universe
293                .world
294                .add_component(engine::ecs::component::ActionComponent::new(
295                    engine::ecs::IntentValue::AttachClone {
296                        parents: vec![parent],
297                        prefab_root: prefab_b,
298                    },
299                ));
300        let _ = universe.attach(kf_attach, attach_action);
301
302        if i >= window {
303            let remove_parent = anchors_b[(i - window) % anchor_count];
304            let kf_remove = universe
305                .world
306                .add_component(engine::ecs::component::KeyframeComponent::new(beat_remove));
307            let _ = universe.attach(anim_b, kf_remove);
308
309            let remove_action =
310                universe
311                    .world
312                    .add_component(engine::ecs::component::ActionComponent::new(
313                        engine::ecs::IntentValue::RemoveChild {
314                            parents: vec![remove_parent],
315                            index: 0,
316                        },
317                    ));
318            let _ = universe.attach(kf_remove, remove_action);
319        }
320    }
321
322    // Phase 2: cleanup tail so the loop doesn't accumulate cubes.
323    for j in 0..window {
324        let remove_parent = anchors_b[(steps_b - window + j) % anchor_count];
325        let beat_remove = steps_b as f64 + j as f64 + 0.05;
326
327        let kf_remove = universe
328            .world
329            .add_component(engine::ecs::component::KeyframeComponent::new(beat_remove));
330        let _ = universe.attach(anim_b, kf_remove);
331
332        let remove_action =
333            universe
334                .world
335                .add_component(engine::ecs::component::ActionComponent::new(
336                    engine::ecs::IntentValue::RemoveChild {
337                        parents: vec![remove_parent],
338                        index: 0,
339                    },
340                ));
341        let _ = universe.attach(kf_remove, remove_action);
342    }
343
344    universe.add(anim_b);
345
346    // --- Grid C: move cubes via set_position (no topology changes) ---
347    let grid_c_root = universe
348        .world
349        .add_component(engine::ecs::component::TransformComponent::new());
350    let _ = universe.attach(viz_root, grid_c_root);
351
352    let mut anchors_c: Vec<(f32, f32, f32)> = Vec::with_capacity(anchor_count);
353    for i in 0..anchor_count {
354        let (x, y, z) = grid_anchor_local(layout_c, i);
355        anchors_c.push((x, y, z));
356
357        let anchor = universe.world.add_component(
358            engine::ecs::component::TransformComponent::new().with_position(x, y, z),
359        );
360        let _ = universe.attach(grid_c_root, anchor);
361        spawn_emissive_marker_cube(
362            &mut universe,
363            anchor,
364            (0.0, -0.35, 0.0),
365            0.06,
366            [0.18, 0.22, 0.18, 1.0],
367        );
368    }
369
370    let mut cubes_c: Vec<engine::ecs::ComponentId> = Vec::with_capacity(cube_pool_size);
371    for i in 0..cube_pool_size {
372        let t = (i as f32) / ((cube_pool_size - 1) as f32).max(1.0);
373        let rgba = [0.25, 0.85 - 0.55 * t, 0.25 + 0.55 * t, 1.0];
374        let cube_root = spawn_detached_cube_prefab(&mut universe, 0.22, rgba);
375        let _ = universe.attach(grid_c_root, cube_root);
376        cubes_c.push(cube_root);
377    }
378
379    let anim_c = universe
380        .world
381        .add_component(engine::ecs::component::AnimationComponent::new());
382    for i in 0..anchor_count {
383        let kf = universe
384            .world
385            .add_component(engine::ecs::component::KeyframeComponent::new(i as f64));
386        let _ = universe.attach(anim_c, kf);
387
388        let cube = cubes_c[i % cube_pool_size];
389        let (x, y, z) = anchors_c[i];
390        let setpos_action =
391            universe
392                .world
393                .add_component(engine::ecs::component::ActionComponent::new(
394                    engine::ecs::IntentValue::SetPosition {
395                        component_ids: vec![cube],
396                        position: [x, y, z],
397                    },
398                ));
399        let _ = universe.attach(kf, setpos_action);
400    }
401    universe.add(anim_c);
402
403    universe.systems.process_commands(
404        &mut universe.world,
405        &mut universe.visuals,
406        &mut universe.render_assets,
407        &mut universe.command_queue,
408    );
409
410    engine::Windowing::run_app(universe).expect("Windowing failed");
411}
examples/animation-example.rs (lines 271-283)
6fn main() {
7    mittens_engine::example_support::ensure_model_assets();
8    utils::logger::init();
9
10    let world = engine::ecs::World::default();
11    let mut universe = engine::Universe::new(world);
12
13    // Minimal scene with a camera so the window opens.
14    let clear = universe
15        .world
16        .add_component(engine::ecs::component::BackgroundColorComponent::new());
17    let clear_c = universe
18        .world
19        .add_component(engine::ecs::component::ColorComponent::rgba(
20            0.08, 0.08, 0.08, 1.0,
21        ));
22    let _ = universe.world.add_child(clear, clear_c);
23    universe.add(clear);
24
25    // Input-driven camera rig.
26    let input = universe
27        .world
28        .add_component(engine::ecs::component::InputComponent::new().with_speed(2.0));
29    let rig_transform = universe.world.add_component(
30        engine::ecs::component::TransformComponent::new().with_position(2.0, 0.0, 7.0),
31    );
32    let input_mode = universe.world.add_component(
33        engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
34    );
35    let camera3d = universe.world.add_component(
36        engine::ecs::component::Camera3DComponent::new()
37            .with_far(250.0)
38            .with_fov(70.0),
39    );
40    let _ = universe.attach(input, input_mode);
41    let _ = universe.attach(input, rig_transform);
42    let _ = universe.attach(rig_transform, camera3d);
43
44    // Topology: I { T { C3D } } — add a small camera-attached controls hint.
45    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
46    universe.add(input);
47
48    // Light so we can see non-emissive materials (even though our cubes are emissive).
49    let light_tx = universe.world.add_component(
50        engine::ecs::component::TransformComponent::new().with_position(0.0, 2.5, 4.0),
51    );
52    let light = universe.world.add_component(
53        engine::ecs::component::PointLightComponent::new()
54            .with_distance(25.0)
55            .with_color(1.0, 1.0, 1.0),
56    );
57    let _ = universe.attach(light_tx, light);
58    universe.add(light_tx);
59
60    // ClockComponent sets global tempo.
61    let clock = universe
62        .world
63        .add_component(engine::ecs::component::ClockComponent::new().with_bpm(128.0));
64    universe.add(clock);
65
66    // Audio output + oscillators driven by scheduled actions.
67    let audio_out = universe
68        .world
69        .add_component(engine::ecs::component::AudioOutputComponent::new());
70    universe.add(audio_out);
71
72    // Noise oscillator: short white-noise hits.
73    let osc_noise = engine::ecs::component::AudioOscillator::noise()
74        .with_frequency(0.0)
75        .with_amplitude(0.06)
76        .with_enabled(false);
77    let osc_noise_comp =
78        universe
79            .world
80            .add_component(engine::ecs::component::AudioOscillatorComponent::single(
81                osc_noise,
82            ));
83    let _ = universe.attach(audio_out, osc_noise_comp);
84
85    // Drum oscillator: retriggers (phase + sweep) every enable.
86    let osc_drum = engine::ecs::component::AudioOscillator::drum()
87        // A low-ish pitch scale; actual pitch is set by scheduled notes.
88        .with_frequency(32.0)
89        .with_amplitude(0.40)
90        .with_enabled(false);
91    let osc_drum_comp =
92        universe
93            .world
94            .add_component(engine::ecs::component::AudioOscillatorComponent::single(
95                osc_drum,
96            ));
97
98    let _ = universe.attach(audio_out, osc_drum_comp);
99
100    // --- Visual layout helpers ---
101    fn spawn_text(universe: &mut engine::Universe, pos: (f32, f32, f32), scale: f32, text: &str) {
102        let tx = universe.world.add_component(
103            engine::ecs::component::TransformComponent::new()
104                .with_position(pos.0, pos.1, pos.2)
105                .with_scale(scale, scale, 1.0),
106        );
107        let t =
108            universe
109                .world
110                .add_component(engine::ecs::component::TextComponent::with_word_wrap(
111                    text, 38,
112                ));
113        let _ = universe.attach(tx, t);
114        universe.add(tx);
115    }
116
117    fn spawn_emissive_cube(
118        universe: &mut engine::Universe,
119        parent: engine::ecs::ComponentId,
120        pos: (f32, f32, f32),
121        scale: f32,
122        rgba: [f32; 4],
123    ) {
124        let tx = universe.world.add_component(
125            engine::ecs::component::TransformComponent::new()
126                .with_position(pos.0, pos.1, pos.2)
127                .with_scale(scale, scale, scale),
128        );
129        let r = universe
130            .world
131            .add_component(engine::ecs::component::RenderableComponent::cube());
132        let c = universe
133            .world
134            .add_component(engine::ecs::component::ColorComponent::rgba(
135                rgba[0], rgba[1], rgba[2], rgba[3],
136            ));
137        let e = universe
138            .world
139            .add_component(engine::ecs::component::EmissiveComponent::on());
140        let _ = universe.attach(parent, tx);
141        let _ = universe.attach(tx, r);
142        let _ = universe.attach(r, c);
143        let _ = universe.attach(r, e);
144    }
145
146    fn spawn_op_cube(
147        universe: &mut engine::Universe,
148        parent: engine::ecs::ComponentId,
149        pos: (f32, f32, f32),
150        scale: f32,
151        base_rgba: [f32; 4],
152    ) -> engine::ecs::ComponentId {
153        let tx = universe.world.add_component(
154            engine::ecs::component::TransformComponent::new()
155                .with_position(pos.0, pos.1, pos.2)
156                .with_scale(scale, scale, scale),
157        );
158        let r = universe
159            .world
160            .add_component(engine::ecs::component::RenderableComponent::cube());
161        let c = universe
162            .world
163            .add_component(engine::ecs::component::ColorComponent::rgba(
164                base_rgba[0],
165                base_rgba[1],
166                base_rgba[2],
167                base_rgba[3],
168            ));
169        let e = universe
170            .world
171            .add_component(engine::ecs::component::EmissiveComponent::on());
172
173        let _ = universe.attach(parent, tx);
174        let _ = universe.attach(tx, r);
175        let _ = universe.attach(r, c);
176        let _ = universe.attach(r, e);
177
178        tx
179    }
180
181    // --- HUD / lanes ---
182    let lane_x = -2.6_f32;
183    let lane_title_z = -0.4_f32;
184    let lane_cfg_z = -0.4_f32;
185    let _lane_cube_z = -0.8_f32;
186    let drum_y = 0.9_f32;
187    let noise_y = -0.4_f32;
188
189    spawn_text(
190        &mut universe,
191        (lane_x, drum_y + 0.55, lane_title_z),
192        0.09,
193        "Audio Source A: Drum (kick)",
194    );
195    spawn_text(
196        &mut universe,
197        (lane_x, drum_y + 0.25, lane_cfg_z),
198        0.07,
199        "type=Drum\namp=0.40\nbase_freq=32Hz\nkick: C0 dur=0.12 vel=0.90\nlookahead=0.10s",
200    );
201
202    spawn_text(
203        &mut universe,
204        (lane_x, noise_y + 0.55, lane_title_z),
205        0.09,
206        "Audio Source B: Noise",
207    );
208    spawn_text(
209        &mut universe,
210        (lane_x, noise_y + 0.25, lane_cfg_z),
211        0.07,
212        "type=Noise\namp=0.06\nnoise: C9 dur=0.06 vel=0.25\noffset=+0.5 beats\nlookahead=0.10s",
213    );
214
215    // Representative cubes for the two audio sources.
216    let viz_root = universe.world.add_component(
217        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
218    );
219    universe.add(viz_root);
220    spawn_emissive_cube(
221        &mut universe,
222        viz_root,
223        (lane_x - 0.28, drum_y + 0.55, lane_title_z),
224        0.16,
225        [1.00, 0.90, 0.10, 1.0],
226    );
227    spawn_emissive_cube(
228        &mut universe,
229        viz_root,
230        (lane_x - 0.28, noise_y + 0.55, lane_title_z),
231        0.16,
232        [1.00, 1.00, 1.00, 1.0],
233    );
234
235    // Timeline roots so we can "reset all" via a single set_color action.
236    let kick_timeline_root = universe.world.add_component(
237        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
238    );
239    let noise_timeline_root = universe.world.add_component(
240        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
241    );
242    let _ = universe.attach(viz_root, kick_timeline_root);
243    let _ = universe.attach(viz_root, noise_timeline_root);
244
245    // Animation with 16 keyframes: each keyframe schedules a kick at offset 0.0,
246    // and a noise hit at offset 0.5.
247    let anim = universe
248        .world
249        .add_component(engine::ecs::component::AnimationComponent::new());
250
251    let kick_dur = 0.12_f32;
252    let noise_dur = 0.06_f32;
253
254    // Requested palette:
255    // - kick: dark yellow -> bright yellow
256    // - noise: grey -> white
257    let kick_dark = [0.35, 0.28, 0.02, 1.0];
258    let kick_bright = [1.00, 0.90, 0.10, 1.0];
259    let noise_dark = [0.35, 0.35, 0.35, 1.0];
260    let noise_bright = [1.00, 1.00, 1.00, 1.0];
261
262    let beat_spacing = 0.38_f32;
263    for i in 0..16 {
264        let kf_beat = i as f64;
265        let kf = universe
266            .world
267            .add_component(engine::ecs::component::KeyframeComponent::new(kf_beat));
268
269        let kick = universe
270            .world
271            .add_component(engine::ecs::component::ActionComponent::new(
272                engine::ecs::IntentValue::AudioSchedulePlay {
273                    component_ids: vec![osc_drum_comp],
274                    beat_offset: 0.0,
275                    beat_context: None,
276                    note: Some(
277                        engine::ecs::component::MusicNote::c(0, kick_dur).with_velocity(0.9),
278                    ),
279                    gain: None,
280                    rate: None,
281                    duration: None,
282                },
283            ));
284
285        let noise = universe
286            .world
287            .add_component(engine::ecs::component::ActionComponent::new(
288                engine::ecs::IntentValue::AudioSchedulePlay {
289                    component_ids: vec![osc_noise_comp],
290                    beat_offset: 0.5,
291                    beat_context: None,
292                    note: Some(
293                        engine::ecs::component::MusicNote::c(9, noise_dur).with_velocity(0.25),
294                    ),
295                    gain: None,
296                    rate: None,
297                    duration: None,
298                },
299            ));
300
301        let _ = universe.attach(anim, kf);
302        let _ = universe.attach(kf, kick);
303        let _ = universe.attach(kf, noise);
304
305        // Each keyframe drives visualization purely via normal actions:
306        // 1) reset all timeline cubes to their base colors
307        // 2) brighten the current beat's cubes
308        let reset_kick_lane =
309            universe
310                .world
311                .add_component(engine::ecs::component::ActionComponent::new(
312                    engine::ecs::IntentValue::SetColor {
313                        component_ids: vec![kick_timeline_root],
314                        rgba: kick_dark,
315                    },
316                ));
317        let reset_noise_lane =
318            universe
319                .world
320                .add_component(engine::ecs::component::ActionComponent::new(
321                    engine::ecs::IntentValue::SetColor {
322                        component_ids: vec![noise_timeline_root],
323                        rgba: noise_dark,
324                    },
325                ));
326        let _ = universe.attach(kf, reset_kick_lane);
327        let _ = universe.attach(kf, reset_noise_lane);
328
329        // Timeline cubes: one cube per scheduled audio operation.
330        // Kick (offset 0.0) on the drum lane.
331        let kick_x = (kf_beat as f32) * beat_spacing;
332        let kick_cube = spawn_op_cube(
333            &mut universe,
334            kick_timeline_root,
335            (kick_x, drum_y, -1.3),
336            0.10,
337            kick_dark,
338        );
339
340        // Noise (offset 0.5) on the noise lane.
341        let noise_x = ((kf_beat + 0.5) as f32) * beat_spacing;
342        let noise_cube = spawn_op_cube(
343            &mut universe,
344            noise_timeline_root,
345            (noise_x, noise_y, -1.3),
346            0.10,
347            noise_dark,
348        );
349
350        let brighten_kick =
351            universe
352                .world
353                .add_component(engine::ecs::component::ActionComponent::new(
354                    engine::ecs::IntentValue::SetColor {
355                        component_ids: vec![kick_cube],
356                        rgba: kick_bright,
357                    },
358                ));
359        let brighten_noise =
360            universe
361                .world
362                .add_component(engine::ecs::component::ActionComponent::new(
363                    engine::ecs::IntentValue::SetColor {
364                        component_ids: vec![noise_cube],
365                        rgba: noise_bright,
366                    },
367                ));
368        let _ = universe.attach(kf, brighten_kick);
369        let _ = universe.attach(kf, brighten_noise);
370    }
371
372    universe.add(anim);
373
374    universe.systems.process_commands(
375        &mut universe.world,
376        &mut universe.visuals,
377        &mut universe.render_assets,
378        &mut universe.command_queue,
379    );
380
381    engine::Windowing::run_app(universe).expect("Windowing failed");
382}
examples/audio-graph-example.rs (lines 654-664)
6fn main() {
7    mittens_engine::example_support::ensure_model_assets();
8    utils::logger::init();
9
10    // Debug toggle: allow isolating stack overflows to audio vs. non-audio init.
11    // Set `CAT_AUDIO_EXAMPLE_DISABLE_AUDIO=1` to skip creating audio components and
12    // skip scheduling audio notes. The UI/text/cube visualization will still spawn.
13    let audio_enabled = std::env::var("CAT_AUDIO_EXAMPLE_DISABLE_AUDIO")
14        .ok()
15        .as_deref()
16        != Some("1");
17
18    // If set, we still build audio graph components but we don't start the CPAL output stream.
19    // This helps distinguish "audio graph / scheduling" issues from "CPAL backend" issues.
20    let audio_output_enabled = std::env::var("CAT_AUDIO_EXAMPLE_AUDIO_OUTPUT_OFF")
21        .ok()
22        .as_deref()
23        != Some("1");
24
25    println!("[audio-graph-example] start");
26
27    let world = engine::ecs::World::default();
28    let mut universe = engine::Universe::new(world);
29
30    println!("[audio-graph-example] universe created");
31
32    // Minimal scene with a camera so the window opens (copied from animation-example).
33    let clear = universe
34        .world
35        .add_component(engine::ecs::component::BackgroundColorComponent::new());
36    let clear_c = universe
37        .world
38        .add_component(engine::ecs::component::ColorComponent::rgba(
39            0.07, 0.07, 0.07, 1.0,
40        ));
41    let _ = universe.world.add_child(clear, clear_c);
42    universe.add(clear);
43
44    // Ambient light so unlit areas aren't pitch black.
45    // Keep it dark to match the background clear color.
46    // (User request) 2.5x brighter.
47    let ambient = universe
48        .world
49        .add_component(engine::ecs::component::AmbientLightComponent::rgb(
50            0.075, 0.075, 0.075,
51        ));
52    universe.add(ambient);
53
54    // Input-driven camera rig.
55    let input = universe
56        .world
57        .add_component(engine::ecs::component::InputComponent::new().with_speed(2.0));
58    let rig_transform = universe.world.add_component(
59        engine::ecs::component::TransformComponent::new().with_position(2.0, 0.0, 7.0),
60    );
61    let input_mode = universe.world.add_component(
62        engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
63    );
64    let camera3d = universe.world.add_component(
65        engine::ecs::component::Camera3DComponent::new()
66            .with_far(250.0)
67            .with_fov(70.0),
68    );
69    let _ = universe.attach(input, input_mode);
70    let _ = universe.attach(input, rig_transform);
71    let _ = universe.attach(rig_transform, camera3d);
72
73    // Topology: I { T { C3D } } — add a small camera-attached controls hint.
74    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
75    universe.add(input);
76
77    // Directional light (sun-ish). Note: the renderer interprets the node's world position
78    // as a direction vector (see DirectionalLightComponent docs).
79    let light_tx = universe.world.add_component(
80        engine::ecs::component::TransformComponent::new().with_position(0.2, 0.7, 1.0),
81    );
82    let light = universe.world.add_component(
83        engine::ecs::component::DirectionalLightComponent::new()
84            .with_color(1.0, 1.0, 1.0)
85            .with_intensity(0.35),
86    );
87    let _ = universe.attach(light_tx, light);
88    universe.add(light_tx);
89
90    // --- Background clouds (occluded + lit) ---
91    // Mirrors the vtuber example: use a BackgroundComponent stage so the cloud volume
92    // self-occludes and is lit, but renders as background.
93    let bg_root = universe.world.add_component(
94        engine::ecs::component::BackgroundComponent::new().with_occlusion_and_lighting(),
95    );
96    universe.add(bg_root);
97    let mut cloud_params = example_util::CloudRingParams::default();
98    cloud_params.cloud_count = 7;
99    cloud_params.radius = 22.0;
100    // Move the ring up by ~one cloud height. The cloud generator uses a ~4.0 unit
101    // vertical spread for puff offsets, so +4.0 is a good "one height" bump.
102    cloud_params.center_y = 6.0;
103    cloud_params.puffs_per_cloud = 26;
104    cloud_params.angle_jitter = 0.0;
105    cloud_params.high_y_probability = 0.0;
106    cloud_params.high_y_multiplier = 1.0;
107    cloud_params.seed = 0xA0_D1_0C_01u32;
108    example_util::spawn_cloud_ring(&mut universe, bg_root, cloud_params);
109
110    // ClockComponent sets global tempo.
111    if audio_enabled {
112        let clock = universe
113            .world
114            .add_component(engine::ecs::component::ClockComponent::new().with_bpm(128.0));
115        universe.add(clock);
116    }
117
118    if audio_enabled {
119        println!("[audio-graph-example] clock added");
120    } else {
121        println!("[audio-graph-example] audio disabled; skipping clock");
122    }
123
124    // Audio output + 2 oscillator sources.
125    let audio_out = if audio_enabled {
126        let audio_out_comp = if audio_output_enabled {
127            engine::ecs::component::AudioOutputComponent::new()
128        } else {
129            engine::ecs::component::AudioOutputComponent::off()
130        };
131        let audio_out = universe.world.add_component(audio_out_comp);
132        universe.add(audio_out);
133        Some(audio_out)
134    } else {
135        None
136    };
137
138    match (audio_enabled, audio_output_enabled) {
139        (false, _) => println!("[audio-graph-example] audio disabled; skipping audio output"),
140        (true, true) => println!("[audio-graph-example] audio output added (CPAL on)"),
141        (true, false) => println!("[audio-graph-example] audio output added (CPAL off)"),
142    }
143
144    // Track A: square lead.
145    let osc_a_comp =
146        if audio_enabled {
147            let osc_a = engine::ecs::component::AudioOscillator::square()
148                .with_frequency(110.0)
149                .with_amplitude(0.12)
150                .with_enabled(false);
151            let osc_a_comp = universe.world.add_component(
152                engine::ecs::component::AudioOscillatorComponent::single(osc_a),
153            );
154            if let Some(audio_out) = audio_out {
155                let _ = universe.attach(audio_out, osc_a_comp);
156            }
157            Some(osc_a_comp)
158        } else {
159            None
160        };
161
162    if audio_enabled {
163        println!("[audio-graph-example] track A created");
164    } else {
165        println!("[audio-graph-example] audio disabled; skipping track A");
166    }
167
168    // Effect tree A (single chain):
169    // osc_a
170    //   Gain
171    //     BandPass
172    //       Limiter
173    let mut bp_a_comp: Option<engine::ecs::ComponentId> = None;
174    if let Some(osc_a_comp) = osc_a_comp {
175        let gain_a = universe
176            .world
177            .add_component(engine::ecs::component::AudioGainComponent::new(3.2));
178        let bp_a = universe.world.add_component(
179            engine::ecs::component::AudioBandPassFilterComponent::new(120.0, 3.0, 0.40),
180        );
181        bp_a_comp = Some(bp_a);
182        let lim_a =
183            universe
184                .world
185                .add_component(engine::ecs::component::AudioLimiterComponent::new(
186                    4.0, 80.0, 0.90,
187                ));
188
189        let _ = universe.attach(osc_a_comp, gain_a);
190        let _ = universe.attach(gain_a, bp_a);
191        let _ = universe.attach(bp_a, lim_a);
192    }
193
194    if audio_enabled {
195        println!("[audio-graph-example] track A effects attached");
196    }
197
198    // --- Visual layout helpers (copied/adapted from animation-example) ---
199    fn spawn_text(
200        universe: &mut engine::Universe,
201        pos: (f32, f32, f32),
202        scale: f32,
203        wrap_cols: usize,
204        text: &str,
205    ) {
206        let tx = universe.world.add_component(
207            engine::ecs::component::TransformComponent::new()
208                .with_position(pos.0, pos.1, pos.2)
209                .with_scale(scale, scale, 1.0),
210        );
211        let t =
212            universe
213                .world
214                .add_component(engine::ecs::component::TextComponent::with_word_wrap(
215                    text, wrap_cols,
216                ));
217        let _ = universe.attach(tx, t);
218
219        // TextSystem looks for an immediate TextureFilteringComponent child.
220        let filtering = universe
221            .world
222            .add_component(engine::ecs::component::TextureFilteringComponent::nearest());
223        let _ = universe.attach(t, filtering);
224
225        // TextSystem also supports styling from immediate Emissive children.
226        let emissive = universe
227            .world
228            .add_component(engine::ecs::component::EmissiveComponent::on());
229        let _ = universe.attach(t, emissive);
230
231        universe.add(tx);
232    }
233
234    fn spawn_emissive_cube(
235        universe: &mut engine::Universe,
236        parent: engine::ecs::ComponentId,
237        pos: (f32, f32, f32),
238        scale: f32,
239        rgba: [f32; 4],
240    ) {
241        let tx = universe.world.add_component(
242            engine::ecs::component::TransformComponent::new()
243                .with_position(pos.0, pos.1, pos.2)
244                .with_scale(scale, scale, scale),
245        );
246        let r = universe
247            .world
248            .add_component(engine::ecs::component::RenderableComponent::cube());
249        let c = universe
250            .world
251            .add_component(engine::ecs::component::ColorComponent::rgba(
252                rgba[0], rgba[1], rgba[2], rgba[3],
253            ));
254        let e = universe
255            .world
256            .add_component(engine::ecs::component::EmissiveComponent::on());
257        let _ = universe.attach(parent, tx);
258        let _ = universe.attach(tx, r);
259        let _ = universe.attach(r, c);
260        let _ = universe.attach(r, e);
261    }
262
263    fn spawn_op_cube(
264        universe: &mut engine::Universe,
265        parent: engine::ecs::ComponentId,
266        pos: (f32, f32, f32),
267        scale: f32,
268        base_rgba: [f32; 4],
269    ) -> engine::ecs::ComponentId {
270        let tx = universe.world.add_component(
271            engine::ecs::component::TransformComponent::new()
272                .with_position(pos.0, pos.1, pos.2)
273                .with_scale(scale, scale, scale),
274        );
275        let r = universe
276            .world
277            .add_component(engine::ecs::component::RenderableComponent::cube());
278        let c = universe
279            .world
280            .add_component(engine::ecs::component::ColorComponent::rgba(
281                base_rgba[0],
282                base_rgba[1],
283                base_rgba[2],
284                base_rgba[3],
285            ));
286        let e = universe
287            .world
288            .add_component(engine::ecs::component::EmissiveComponent::on());
289
290        let _ = universe.attach(parent, tx);
291        let _ = universe.attach(tx, r);
292        let _ = universe.attach(r, c);
293        let _ = universe.attach(r, e);
294
295        tx
296    }
297
298    // --- HUD / lane (single track) ---
299    let lane_x = -2.6_f32;
300    let lane_title_z = -0.4_f32;
301    let lane_cfg_z = -0.4_f32;
302    let lane_pat_z = -1.3_f32;
303    let lane_graph_z = -1.15_f32;
304
305    // Content for pattern/chain/graph starts at x=0; keep section labels aligned there.
306    // This also keeps them from overlapping the config block (which is anchored at lane_x).
307    let lane_labels_x = lane_x + 1.6_f32;
308
309    let track_a_y = 0.9_f32;
310
311    spawn_text(
312        &mut universe,
313        (lane_x, track_a_y + 0.55, lane_title_z),
314        0.09,
315        42,
316        "Track A: AudioOscillator::square()",
317    );
318    spawn_text(
319        &mut universe,
320        (lane_x, track_a_y + 0.25, lane_cfg_z),
321        0.07,
322        48,
323        "oscillators=1\nfrequency_hz=110.0\namplitude=0.12\nenabled=false\nlookahead=0.10s",
324    );
325
326    let viz_root = universe.world.add_component(
327        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
328    );
329    universe.add(viz_root);
330
331    println!("[audio-graph-example] viz root added");
332
333    // Small identifier cubes next to titles.
334    spawn_emissive_cube(
335        &mut universe,
336        viz_root,
337        (lane_x - 0.28, track_a_y + 0.55, lane_title_z),
338        0.16,
339        [0.85, 0.40, 1.00, 1.0],
340    );
341
342    // Pattern roots so we can reset via one SetColor action.
343    let track_a_pattern_root = universe.world.add_component(
344        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
345    );
346    let _ = universe.attach(viz_root, track_a_pattern_root);
347
348    // Labels for pattern/chain/graph sections.
349    spawn_text(
350        &mut universe,
351        (lane_labels_x, track_a_y, lane_title_z),
352        0.06,
353        42,
354        "pattern",
355    );
356    spawn_text(
357        &mut universe,
358        (lane_labels_x, track_a_y - 0.40, lane_title_z),
359        0.06,
360        42,
361        "graph",
362    );
363
364    // --- Processing chain + graph visualization (compiled graph → cubes + labels) ---
365    use engine::ecs::system::audio_graph_compiler::{
366        AudioGraphCompiler, AudioGraphNode, AudioGraphNodeKind,
367    };
368
369    fn effect_grey_for_depth(depth: usize) -> f32 {
370        // depth=1 => medium grey; deeper => lighter, capped.
371        let base = 0.55;
372        let step = 0.13;
373        let d = depth.saturating_sub(1) as f32;
374        (base + step * d).min(0.92)
375    }
376
377    fn node_rgba(node: &AudioGraphNode, depth: usize) -> [f32; 4] {
378        match node.kind {
379            AudioGraphNodeKind::OscillatorSource { .. } => [1.0, 0.78, 0.22, 1.0],
380            _ => {
381                let g = effect_grey_for_depth(depth);
382                [g, g, g, 1.0]
383            }
384        }
385    }
386
387    fn node_label(node: &AudioGraphNode) -> String {
388        match &node.kind {
389            AudioGraphNodeKind::OscillatorSource { voices } => {
390                format!("OscillatorSource voices={voices}")
391            }
392            AudioGraphNodeKind::Gain { gain } => {
393                format!("Gain gain={gain:.3}")
394            }
395            AudioGraphNodeKind::LowPass {
396                cutoff_hz,
397                resonance,
398            } => {
399                format!("LowPass cutoff={cutoff_hz:.1}Hz res={resonance:.3}")
400            }
401            AudioGraphNodeKind::BandPass {
402                center_hz,
403                bandwidth_octaves,
404                resonance,
405            } => {
406                format!(
407                    "BandPass center={center_hz:.1}Hz bw={bandwidth_octaves:.3}oct res={resonance:.3}"
408                )
409            }
410            AudioGraphNodeKind::HighPass {
411                cutoff_hz,
412                resonance,
413            } => {
414                format!("HighPass cutoff={cutoff_hz:.1}Hz res={resonance:.3}")
415            }
416            AudioGraphNodeKind::Limiter {
417                attack_ms,
418                release_ms,
419                threshold,
420            } => {
421                format!("Limiter atk={attack_ms:.1}ms rel={release_ms:.1}ms thr={threshold:.3}")
422            }
423            AudioGraphNodeKind::ClipSource => "ClipSource".to_string(),
424        }
425    }
426
427    fn compute_layout(
428        node: &AudioGraphNode,
429        depth: usize,
430        x_cursor: &mut i32,
431        out: &mut std::collections::HashMap<*const AudioGraphNode, (i32, usize)>,
432    ) -> i32 {
433        // Depth-only layout:
434        // - keep children directly below their parent on X
435        // - use Z sibling offsets (in spawn_graph_tree) to show branching
436        let _ = x_cursor;
437        for ch in node.children.iter() {
438            let _ = compute_layout(ch, depth + 1, x_cursor, out);
439        }
440
441        let my_x = 0;
442        out.insert(node as *const AudioGraphNode, (my_x, depth));
443        my_x
444    }
445
446    fn spawn_graph_tree(
447        universe: &mut engine::Universe,
448        parent: engine::ecs::ComponentId,
449        node: &AudioGraphNode,
450        bp_component: Option<engine::ecs::ComponentId>,
451        bp_label_out: &mut Option<engine::ecs::ComponentId>,
452        origin: (f32, f32, f32),
453        layout: &std::collections::HashMap<*const AudioGraphNode, (i32, usize)>,
454        dx: f32,
455        dy: f32,
456        cube_scale: f32,
457        sibling_index: usize,
458        sibling_count: usize,
459    ) {
460        let Some((x_unit, depth)) = layout.get(&(node as *const AudioGraphNode)).copied() else {
461            return;
462        };
463
464        let x = origin.0 + (x_unit as f32) * dx;
465        let y = origin.1 - (depth as f32) * dy;
466
467        // Push siblings “behind” each other along Z to reduce overlap between
468        // a sibling's cube and another node's label.
469        let dz_sibling = 0.18;
470        let z = origin.2 - (sibling_index as f32) * dz_sibling;
471
472        // Keep text slightly in front of its cube.
473        let z_text = z + 0.03;
474
475        let rgba = node_rgba(node, depth);
476        let cube_tx = spawn_op_cube(universe, parent, (x, y, z), cube_scale, rgba);
477        let _ = cube_tx;
478
479        // Label next to the cube.
480        let label = node_label(node);
481        let tx = universe.world.add_component(
482            engine::ecs::component::TransformComponent::new()
483                .with_position(x + 0.14, y + 0.02, z_text)
484                .with_scale(0.06, 0.06, 1.0),
485        );
486        let t =
487            universe
488                .world
489                .add_component(engine::ecs::component::TextComponent::with_word_wrap(
490                    &label, 25,
491                ));
492        let _ = universe.attach(parent, tx);
493        let _ = universe.attach(tx, t);
494
495        if bp_component == Some(node.component) {
496            *bp_label_out = Some(t);
497        }
498
499        let filtering = universe
500            .world
501            .add_component(engine::ecs::component::TextureFilteringComponent::nearest());
502        let _ = universe.attach(t, filtering);
503
504        let emissive = universe
505            .world
506            .add_component(engine::ecs::component::EmissiveComponent::on());
507        let _ = universe.attach(t, emissive);
508
509        universe.add(tx);
510
511        // Mix/branch label if branching.
512        if node.children.len() > 1 {
513            let mut weights: Vec<f32> = Vec::with_capacity(node.children.len());
514            for i in 0..node.children.len() {
515                let w = node
516                    .mix
517                    .as_ref()
518                    .map(|m| m.weights.get(i).copied().unwrap_or(1.0))
519                    .unwrap_or(1.0);
520                weights.push(w);
521            }
522            let mix_label = if node.mix.is_some() {
523                format!("Mix weights={weights:?}")
524            } else {
525                format!("Mix <implicit> w={weights:?}")
526            };
527            let mix_tx = universe.world.add_component(
528                engine::ecs::component::TransformComponent::new()
529                    .with_position(x + 0.14, y - 0.11, z_text)
530                    .with_scale(0.05, 0.05, 1.0),
531            );
532            let mix_t = universe.world.add_component(
533                engine::ecs::component::TextComponent::with_word_wrap(&mix_label, 25),
534            );
535            let _ = universe.attach(parent, mix_tx);
536            let _ = universe.attach(mix_tx, mix_t);
537
538            let filtering = universe
539                .world
540                .add_component(engine::ecs::component::TextureFilteringComponent::nearest());
541            let _ = universe.attach(mix_t, filtering);
542
543            let emissive = universe
544                .world
545                .add_component(engine::ecs::component::EmissiveComponent::on());
546            let _ = universe.attach(mix_t, emissive);
547
548            universe.add(mix_tx);
549        }
550
551        let child_count = node.children.len().max(1);
552        for (i, ch) in node.children.iter().enumerate() {
553            // Each node's children form a sibling group; offset them in Z.
554            // For the root node, sibling_index is 0.
555            let _ = sibling_count;
556            spawn_graph_tree(
557                universe,
558                parent,
559                ch,
560                bp_component,
561                bp_label_out,
562                origin,
563                layout,
564                dx,
565                dy,
566                cube_scale,
567                i,
568                child_count,
569            );
570        }
571    }
572
573    // Compile + display chain and graph for the track.
574    let compiled_a = if audio_enabled {
575        println!("[audio-graph-example] compiling graph...");
576        let Some(osc_a_comp) = osc_a_comp else {
577            panic!("audio_enabled but track A was not created");
578        };
579        let compiled_a =
580            AudioGraphCompiler::compile(&universe.world, osc_a_comp).expect("compile A");
581        println!("[audio-graph-example] graph compiled");
582        Some(compiled_a)
583    } else {
584        println!("[audio-graph-example] audio disabled; skipping graph compilation");
585        None
586    };
587
588    // Full compiled graph visualization (tree layout + mix labels).
589    let mut bp_graph_label_text: Option<engine::ecs::ComponentId> = None;
590    if let Some(compiled_a) = &compiled_a {
591        let mut x_cursor = 0;
592        let mut layout: std::collections::HashMap<*const AudioGraphNode, (i32, usize)> =
593            std::collections::HashMap::new();
594        let _root_x = compute_layout(&compiled_a.root, 0, &mut x_cursor, &mut layout);
595
596        // With depth-only X layout, we keep the tree centered at x=0.
597        // Pull the graph up now that we don't show the separate chain view.
598        let origin = (0.0, track_a_y - 0.40, lane_graph_z);
599
600        spawn_graph_tree(
601            &mut universe,
602            viz_root,
603            &compiled_a.root,
604            bp_a_comp,
605            &mut bp_graph_label_text,
606            origin,
607            &layout,
608            0.45,
609            0.28,
610            0.08,
611            0,
612            1,
613        );
614    } else {
615        spawn_text(
616            &mut universe,
617            (0.0, track_a_y - 0.40, lane_graph_z),
618            0.06,
619            60,
620            "(audio disabled)",
621        );
622    }
623
624    // Animation with 16 keyframes drives scheduled notes + pattern highlights.
625    let anim = universe
626        .world
627        .add_component(engine::ecs::component::AnimationComponent::new());
628
629    let dur_a = 0.85_f32;
630    let beat_spacing = 0.38_f32;
631
632    let a_dark = [0.18, 0.08, 0.22, 1.0];
633    let a_bright = [0.85, 0.40, 1.00, 1.0];
634
635    for i in 0..16 {
636        let kf_beat = i as f64;
637        let kf = universe
638            .world
639            .add_component(engine::ecs::component::KeyframeComponent::new(kf_beat));
640
641        let _ = universe.attach(anim, kf);
642
643        // Scheduled audio note (sample-accurate via lookahead).
644        // Pulse every beat.
645        if audio_enabled {
646            let Some(osc_a_comp) = osc_a_comp else {
647                panic!("audio_enabled but track A was not created");
648            };
649
650            let note_a = engine::ecs::component::MusicNote::c(1, dur_a).with_velocity(0.80);
651
652            let act_a = universe
653                .world
654                .add_component(engine::ecs::component::ActionComponent::new(
655                    engine::ecs::IntentValue::AudioSchedulePlay {
656                        component_ids: vec![osc_a_comp],
657                        beat_offset: 0.0,
658                        beat_context: None,
659                        note: Some(note_a),
660                        gain: None,
661                        rate: None,
662                        duration: None,
663                    },
664                ));
665
666            let _ = universe.attach(kf, act_a);
667        }
668
669        // Keyframed band-pass center.
670        // This is an immediate parameter update applied RT-side (no graph rebuild).
671        if audio_enabled {
672            if let Some(bp_a_comp) = bp_a_comp {
673                let t = (i as f32) / 15.0;
674                let center_hz = 10.0 + t * (1000.0 - 10.0);
675
676                let bp_center =
677                    universe
678                        .world
679                        .add_component(engine::ecs::component::ActionComponent::new(
680                            engine::ecs::IntentValue::AudioBandPassSetCenterHz {
681                                component_ids: vec![bp_a_comp],
682                                center_hz,
683                            },
684                        ));
685                let _ = universe.attach(kf, bp_center);
686
687                // Update the BandPass node label in the graph visualization.
688                if let Some(text_id) = bp_graph_label_text {
689                    let label =
690                        universe
691                            .world
692                            .add_component(engine::ecs::component::ActionComponent::new(
693                                engine::ecs::IntentValue::SetText {
694                                    component_ids: vec![text_id],
695                                    text: format!("BandPass center={center_hz:.1}Hz"),
696                                },
697                            ));
698                    let _ = universe.attach(kf, label);
699                }
700            }
701        }
702
703        // Visualization reset per keyframe.
704        let reset_a = universe
705            .world
706            .add_component(engine::ecs::component::ActionComponent::new(
707                engine::ecs::IntentValue::SetColor {
708                    component_ids: vec![track_a_pattern_root],
709                    rgba: a_dark,
710                },
711            ));
712        let _ = universe.attach(kf, reset_a);
713
714        // Pattern cube per step for each track.
715        let x = (kf_beat as f32) * beat_spacing;
716        let cube_a = spawn_op_cube(
717            &mut universe,
718            track_a_pattern_root,
719            (x, track_a_y, lane_pat_z),
720            0.10,
721            a_dark,
722        );
723
724        let bright_a = universe
725            .world
726            .add_component(engine::ecs::component::ActionComponent::new(
727                engine::ecs::IntentValue::SetColor {
728                    component_ids: vec![cube_a],
729                    rgba: a_bright,
730                },
731            ));
732        let _ = universe.attach(kf, bright_a);
733    }
734    universe.add(anim);
735
736    println!("[audio-graph-example] animation created");
737
738    // Keep window open.
739    println!("[audio-graph-example] processing commands...");
740    universe.systems.process_commands(
741        &mut universe.world,
742        &mut universe.visuals,
743        &mut universe.render_assets,
744        &mut universe.command_queue,
745    );
746
747    println!("[audio-graph-example] commands processed; launching window");
748
749    engine::Windowing::run_app(universe).expect("Windowing failed");
750}
Source

pub fn new_authored( signal: IntentValue, target_sources: Vec<ComponentRef>, ) -> Self

Construct from a signal whose ComponentId slots are placeholders plus the authoring sources for each slot (in declaration order). resolved starts false; resolution happens when the owning AnimationSystem first processes this action.

Source

pub fn print(message: impl Into<String>) -> Self

Trait Implementations§

Source§

impl Clone for ActionComponent

Source§

fn clone(&self) -> ActionComponent

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Component for ActionComponent

Source§

fn name(&self) -> &'static str

Short debug/type name for this component kind (e.g. “transform”, “camera”).
Source§

fn as_any(&self) -> &dyn Any

Source§

fn as_any_mut(&mut self) -> &mut dyn Any

Source§

fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)

Called when component is added to the World
Source§

fn to_mms_ast(&self, _world: &World) -> ComponentExpression

Encode this component as an MMS Component Expression AST node. Read more
Source§

fn set_id(&mut self, _component: ComponentId)

Source§

fn cleanup(&mut self, _emit: &mut dyn SignalEmitter, _component: ComponentId)

Called when component is removed from the World.
Source§

fn encode(&self) -> HashMap<String, Value>

Encode component data to a HashMap for serialization. Read more
Source§

fn decode(&mut self, _data: &HashMap<String, Value>) -> Result<(), String>

Decode component data from a HashMap after deserialization. Read more
Source§

impl Debug for ActionComponent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ActionComponent

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<F, T> IntoSample<T> for F
where T: FromSample<F>,

Source§

fn into_sample(self) -> T

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more