1pub mod camera_follow;
43pub mod context;
44pub mod debug_draw;
46pub mod events;
47pub mod guide;
49pub mod jobs;
51pub mod mode;
52pub mod output;
53pub mod plugin;
54pub mod plugins;
56pub mod resources;
57pub mod snapshot;
58pub mod systems;
60pub mod timestep;
61
62pub use camera_follow::CameraFollow;
63pub use context::{RuntimeFrameContext, RuntimeStepContext, SimulationStepContext};
64pub use debug_draw::{DebugDraw, DebugLayer, DebugPrim};
65pub use events::RuntimeEventBus;
66pub use jobs::{JobPoll, JobSender, JobSlot};
67pub use mode::SceneRuntimeMode;
68pub use output::{CameraCommand, ContactEvent, NodeTransformOp, RuntimeOutput, SelectionOp, SkinnedMeshUpdate, SkinnedPoseUpdate, TransformWriteback};
69pub use plugin::{phase, RuntimeEvent, RuntimePhase, RuntimePlugin};
70pub use plugins::{
71 AnimationClip, AnimationPlugin, AnimationTrack, Channel, ClipPlayerPlugin, Constraint,
72 ConstraintPlugin, Interpolation, Joint, JointMatrices, Keyframe, PhysicsBody,
73 PhysicsLitePlugin, Pose, Sampler, Skeleton, SkeletonPlugin, SkinnedActor, SkinnedActorPart,
74 SkinnedActorPlugin, SkinningPath, Track, TrackValue, TrackValues, apply_skin,
75};
76pub use resources::RuntimeResources;
77pub use snapshot::{TransformSnapshot, TransformSnapshotTable};
78pub use systems::{ManipulationSystem, SelectionSystem};
79pub use timestep::{FixedStepIter, FixedTimestep};
80
81use crate::interaction::selection::Selection;
82use crate::scene::scene::Scene;
83
84fn run_range(
89 plugins: &mut Vec<Box<dyn RuntimePlugin>>,
90 min: i32,
91 max: i32,
92 dt: f32,
93 frame: &RuntimeFrameContext,
94 scene: &Scene,
95 writeback: &mut TransformWriteback,
96 output: &mut RuntimeOutput,
97 resources: &mut RuntimeResources,
98) {
99 for plugin in plugins.iter_mut() {
100 let p = plugin.priority();
101 if p >= min && p < max {
102 let mut ctx = RuntimeStepContext {
103 priority: p,
104 dt,
105 scene,
106 writeback,
107 output,
108 pick_hit: frame.pick_hit,
109 resources,
110 };
111 plugin.step(&mut ctx);
112 }
113 }
114}
115
116#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::camera::camera::Camera;
122 use crate::interaction::input::ActionFrame;
123 use crate::interaction::selection::{NodeId, Selection};
124 use crate::scene::material::Material;
125 use crate::scene::scene::Scene;
126 use std::sync::{Arc, Mutex};
127
128 fn make_frame(camera: &Camera, input: &ActionFrame, dt: f32) -> RuntimeFrameContext {
129 RuntimeFrameContext {
130 dt,
131 camera: camera.clone(),
132 viewport_size: glam::Vec2::new(800.0, 600.0),
133 input: input.clone(),
134 pick_hit: None,
135 clicked: false,
136 drag_started: false,
137 dragging: false,
138 pointer_delta: glam::Vec2::ZERO,
139 cursor_viewport: None,
140 shift_held: false,
141 }
142 }
143
144 struct OrderTracker {
146 priority: i32,
147 id: u32,
148 log: Arc<Mutex<Vec<(i32, u32)>>>,
149 }
150
151 impl RuntimePlugin for OrderTracker {
152 fn priority(&self) -> i32 {
153 self.priority
154 }
155 fn step(&mut self, _ctx: &mut RuntimeStepContext<'_>) {
156 self.log.lock().unwrap().push((self.priority, self.id));
157 }
158 }
159
160 struct CallCounter {
162 priority: i32,
163 count: Arc<Mutex<u32>>,
164 }
165
166 impl RuntimePlugin for CallCounter {
167 fn priority(&self) -> i32 {
168 self.priority
169 }
170 fn step(&mut self, _ctx: &mut RuntimeStepContext<'_>) {
171 *self.count.lock().unwrap() += 1;
172 }
173 }
174
175 struct WritebackPlugin {
177 node_id: NodeId,
178 transform: glam::Affine3A,
179 }
180
181 impl RuntimePlugin for WritebackPlugin {
182 fn priority(&self) -> i32 {
183 phase::SIMULATE
184 }
185 fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
186 ctx.writeback.set(self.node_id, self.transform);
187 }
188 }
189
190 struct DtRecorder {
192 dts: Arc<Mutex<Vec<f32>>>,
193 }
194
195 impl RuntimePlugin for DtRecorder {
196 fn priority(&self) -> i32 {
197 phase::SIMULATE
198 }
199 fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
200 self.dts.lock().unwrap().push(ctx.dt);
201 }
202 }
203
204 #[test]
205 fn test_phase_execution_order() {
206 let log = Arc::new(Mutex::new(Vec::<(i32, u32)>::new()));
208 let mut runtime = ViewportRuntime::new()
209 .with_plugin(OrderTracker { priority: phase::WRITEBACK, id: 3, log: log.clone() })
210 .with_plugin(OrderTracker { priority: phase::SIMULATE, id: 2, log: log.clone() })
211 .with_plugin(OrderTracker { priority: phase::ANIMATE, id: 1, log: log.clone() })
212 .with_plugin(OrderTracker { priority: phase::PREPARE, id: 0, log: log.clone() });
213
214 let camera = Camera::default();
215 let input = ActionFrame::default();
216 let mut scene = Scene::new();
217 let mut sel = Selection::new();
218 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
219
220 let calls = log.lock().unwrap();
221 let priorities: Vec<i32> = calls.iter().map(|(p, _)| *p).collect();
222 for w in priorities.windows(2) {
224 assert!(w[0] <= w[1], "expected ascending order, got {:?}", priorities);
225 }
226 assert_eq!(priorities.len(), 4);
227 }
228
229 #[test]
230 fn test_registration_order_within_phase() {
231 let log = Arc::new(Mutex::new(Vec::<(i32, u32)>::new()));
233 let mut runtime = ViewportRuntime::new()
234 .with_plugin(OrderTracker { priority: phase::SIMULATE, id: 0, log: log.clone() })
235 .with_plugin(OrderTracker { priority: phase::SIMULATE, id: 1, log: log.clone() });
236
237 let camera = Camera::default();
238 let input = ActionFrame::default();
239 let mut scene = Scene::new();
240 let mut sel = Selection::new();
241 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
242
243 let calls = log.lock().unwrap();
244 let ids: Vec<u32> = calls.iter().map(|(_, id)| *id).collect();
245 assert_eq!(ids, vec![0, 1]);
246 }
247
248 #[test]
249 fn test_simulate_runs_once_without_fixed_timestep() {
250 let count = Arc::new(Mutex::new(0u32));
251 let mut runtime = ViewportRuntime::new()
252 .with_plugin(CallCounter { priority: phase::SIMULATE, count: count.clone() });
253
254 let camera = Camera::default();
255 let input = ActionFrame::default();
256 let mut scene = Scene::new();
257 let mut sel = Selection::new();
258 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
259
260 assert_eq!(*count.lock().unwrap(), 1);
261 }
262
263 #[test]
264 fn test_simulate_runs_n_times_with_fixed_timestep() {
265 let count = Arc::new(Mutex::new(0u32));
266 let hz = 60.0_f32;
267 let step_dt = 1.0 / hz;
268 let mut runtime = ViewportRuntime::new()
269 .with_fixed_timestep(FixedTimestep::new(hz))
270 .with_plugin(CallCounter { priority: phase::SIMULATE, count: count.clone() });
271
272 let camera = Camera::default();
273 let input = ActionFrame::default();
274 let mut scene = Scene::new();
275 let mut sel = Selection::new();
276
277 let dt = step_dt * 3.0 + 0.0001;
279 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, dt));
280
281 assert_eq!(*count.lock().unwrap(), 3);
282 }
283
284 #[test]
285 fn test_simulate_dt_equals_step_dt_with_fixed_timestep() {
286 let dts = Arc::new(Mutex::new(Vec::<f32>::new()));
287 let hz = 60.0_f32;
288 let step_dt = 1.0 / hz;
289 let mut runtime = ViewportRuntime::new()
290 .with_fixed_timestep(FixedTimestep::new(hz))
291 .with_plugin(DtRecorder { dts: dts.clone() });
292
293 let camera = Camera::default();
294 let input = ActionFrame::default();
295 let mut scene = Scene::new();
296 let mut sel = Selection::new();
297
298 let dt = step_dt * 2.0 + 0.0001;
300 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, dt));
301
302 let recorded = dts.lock().unwrap();
303 assert_eq!(recorded.len(), 2);
304 for &d in recorded.iter() {
305 assert!((d - step_dt).abs() < 1e-6, "expected {step_dt}, got {d}");
306 }
307 }
308
309 #[test]
310 fn test_writeback_flushes_to_scene() {
311 let target = glam::Affine3A::from_translation(glam::Vec3::new(3.0, 4.0, 5.0));
312 let mut scene = Scene::new();
313 let node_id = scene.add(None, glam::Mat4::IDENTITY, Material::default());
314
315 let mut runtime = ViewportRuntime::new()
316 .with_plugin(WritebackPlugin { node_id, transform: target });
317
318 let camera = Camera::default();
319 let input = ActionFrame::default();
320 let mut sel = Selection::new();
321 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
322
323 let node = scene.node(node_id).expect("node not found");
324 let pos = node.world_transform().col(3).truncate();
325 assert!((pos - glam::Vec3::new(3.0, 4.0, 5.0)).length() < 1e-5, "pos was {pos:?}");
326 }
327
328 #[test]
329 fn test_writeback_ops_in_output() {
330 let target = glam::Affine3A::from_translation(glam::Vec3::new(1.0, 0.0, 0.0));
331 let mut scene = Scene::new();
332 let node_id = scene.add(None, glam::Mat4::IDENTITY, Material::default());
333
334 let mut runtime = ViewportRuntime::new()
335 .with_plugin(WritebackPlugin { node_id, transform: target });
336
337 let camera = Camera::default();
338 let input = ActionFrame::default();
339 let mut sel = Selection::new();
340 let output = runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
341
342 assert_eq!(output.node_transform_ops.len(), 1);
343 assert_eq!(output.node_transform_ops[0].id, node_id);
344 }
345
346 #[test]
347 fn test_step_index_increments_each_simulate() {
348 let mut runtime = ViewportRuntime::new();
349 let camera = Camera::default();
350 let input = ActionFrame::default();
351 let mut scene = Scene::new();
352 let mut sel = Selection::new();
353
354 assert_eq!(runtime.step_index(), 0);
355 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
356 assert_eq!(runtime.step_index(), 1);
357 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
358 assert_eq!(runtime.step_index(), 2);
359 }
360
361 #[test]
362 fn test_step_index_increments_n_times_with_fixed_timestep() {
363 let hz = 60.0_f32;
364 let step_dt = 1.0 / hz;
365 let mut runtime = ViewportRuntime::new()
366 .with_fixed_timestep(FixedTimestep::new(hz));
367
368 let camera = Camera::default();
369 let input = ActionFrame::default();
370 let mut scene = Scene::new();
371 let mut sel = Selection::new();
372
373 let dt = step_dt * 3.0 + 0.0001;
375 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, dt));
376 assert_eq!(runtime.step_index(), 3);
377 }
378
379 #[test]
380 fn test_snapshot_updated_after_writeback() {
381 let target = glam::Affine3A::from_translation(glam::Vec3::new(7.0, 0.0, 0.0));
382 let mut scene = Scene::new();
383 let node_id = scene.add(None, glam::Mat4::IDENTITY, Material::default());
384
385 let mut runtime = ViewportRuntime::new()
386 .with_plugin(WritebackPlugin { node_id, transform: target });
387
388 let camera = Camera::default();
389 let input = ActionFrame::default();
390 let mut sel = Selection::new();
391 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
392
393 let snap = runtime.snapshots().get(node_id).expect("snapshot missing");
394 assert!((snap.curr.translation.x - 7.0).abs() < 1e-5);
395 }
396
397 #[test]
400 fn test_post_sim_runs_after_simulate() {
401 let log = Arc::new(Mutex::new(Vec::<(i32, u32)>::new()));
402 let mut runtime = ViewportRuntime::new()
403 .with_plugin(OrderTracker { priority: phase::POST_SIM, id: 1, log: log.clone() })
404 .with_plugin(OrderTracker { priority: phase::SIMULATE, id: 0, log: log.clone() });
405
406 let camera = Camera::default();
407 let input = ActionFrame::default();
408 let mut scene = Scene::new();
409 let mut sel = Selection::new();
410 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
411
412 let calls = log.lock().unwrap();
413 let ids: Vec<u32> = calls.iter().map(|(_, id)| *id).collect();
414 assert_eq!(ids, vec![0, 1], "simulate must run before post_sim");
415 }
416
417 #[test]
418 fn test_plugin_between_bands() {
419 let log = Arc::new(Mutex::new(Vec::<(i32, u32)>::new()));
420 let mid = phase::ANIMATE + 50;
421 let mut runtime = ViewportRuntime::new()
422 .with_plugin(OrderTracker { priority: phase::SIMULATE, id: 2, log: log.clone() })
423 .with_plugin(OrderTracker { priority: mid, id: 1, log: log.clone() })
424 .with_plugin(OrderTracker { priority: phase::ANIMATE, id: 0, log: log.clone() });
425
426 let camera = Camera::default();
427 let input = ActionFrame::default();
428 let mut scene = Scene::new();
429 let mut sel = Selection::new();
430 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
431
432 let calls = log.lock().unwrap();
433 let ids: Vec<u32> = calls.iter().map(|(_, id)| *id).collect();
434 assert_eq!(ids, vec![0, 1, 2], "plugins must execute in priority order");
435 }
436
437 struct EventRecorder {
439 added: Arc<Mutex<Vec<NodeId>>>,
440 removed: Arc<Mutex<Vec<NodeId>>>,
441 }
442
443 impl RuntimePlugin for EventRecorder {
444 fn priority(&self) -> i32 {
445 phase::PREPARE
446 }
447 fn on_event(&mut self, event: &RuntimeEvent, _ctx: &mut RuntimeStepContext<'_>) {
448 match event {
449 RuntimeEvent::NodeAdded(id) => self.added.lock().unwrap().push(*id),
450 RuntimeEvent::NodeRemoved(id) => self.removed.lock().unwrap().push(*id),
451 }
452 }
453 fn step(&mut self, _ctx: &mut RuntimeStepContext<'_>) {}
454 }
455
456 #[test]
457 fn test_lifecycle_events_node_added() {
458 let added = Arc::new(Mutex::new(Vec::<NodeId>::new()));
459 let removed = Arc::new(Mutex::new(Vec::<NodeId>::new()));
460 let mut runtime = ViewportRuntime::new()
461 .with_plugin(EventRecorder { added: added.clone(), removed: removed.clone() });
462
463 let camera = Camera::default();
464 let input = ActionFrame::default();
465 let mut scene = Scene::new();
466 let mut sel = Selection::new();
467
468 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
470 assert!(added.lock().unwrap().is_empty(), "no events on first step");
471
472 let node_id = scene.add(None, glam::Mat4::IDENTITY, Material::default());
474 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
475
476 let a = added.lock().unwrap();
477 assert!(a.contains(&node_id), "expected NodeAdded event for {:?}", node_id);
478 assert!(removed.lock().unwrap().is_empty());
479 }
480
481 #[test]
482 fn test_lifecycle_events_node_removed() {
483 let added = Arc::new(Mutex::new(Vec::<NodeId>::new()));
484 let removed = Arc::new(Mutex::new(Vec::<NodeId>::new()));
485 let mut runtime = ViewportRuntime::new()
486 .with_plugin(EventRecorder { added: added.clone(), removed: removed.clone() });
487
488 let camera = Camera::default();
489 let input = ActionFrame::default();
490 let mut scene = Scene::new();
491 let mut sel = Selection::new();
492
493 let node_id = scene.add(None, glam::Mat4::IDENTITY, Material::default());
494
495 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
497 added.lock().unwrap().clear();
498
499 scene.remove(node_id);
501 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
502
503 let r = removed.lock().unwrap();
504 assert!(r.contains(&node_id), "expected NodeRemoved event for {:?}", node_id);
505 }
506
507 #[derive(Default)]
509 struct LifecycleRecorder {
510 log: Arc<Mutex<Vec<&'static str>>>,
511 }
512
513 impl RuntimePlugin for LifecycleRecorder {
514 fn priority(&self) -> i32 {
515 phase::PREPARE
516 }
517 fn submit(&mut self, _ctx: &RuntimeStepContext<'_>) {
518 self.log.lock().unwrap().push("submit");
519 }
520 fn step(&mut self, _ctx: &mut RuntimeStepContext<'_>) {
521 self.log.lock().unwrap().push("step");
522 }
523 fn collect(&mut self, _ctx: &mut RuntimeStepContext<'_>) {
524 self.log.lock().unwrap().push("collect");
525 }
526 }
527
528 #[test]
529 fn test_submit_collect_called() {
530 let log = Arc::new(Mutex::new(Vec::<&'static str>::new()));
531 let mut runtime = ViewportRuntime::new()
532 .with_plugin(LifecycleRecorder { log: log.clone() });
533
534 let camera = Camera::default();
535 let input = ActionFrame::default();
536 let mut scene = Scene::new();
537 let mut sel = Selection::new();
538
539 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
540
541 let calls = log.lock().unwrap();
542 assert!(calls.contains(&"submit"), "submit must be called");
543 assert!(calls.contains(&"step"), "step must be called");
544 assert!(calls.contains(&"collect"), "collect must be called");
545 let si = calls.iter().position(|&s| s == "submit").unwrap();
547 let st = calls.iter().position(|&s| s == "step").unwrap();
548 let co = calls.iter().position(|&s| s == "collect").unwrap();
549 assert!(si < st, "submit must come before step");
550 assert!(st < co, "step must come before collect");
551 }
552
553 #[test]
556 fn test_resource_insert_get() {
557 let mut res = RuntimeResources::new();
558 res.insert(42u32);
559 assert_eq!(res.get::<u32>(), Some(&42));
560 assert!(res.get::<u64>().is_none());
561 }
562
563 #[test]
564 fn test_resource_insert_overwrites() {
565 let mut res = RuntimeResources::new();
566 res.insert(1u32);
567 res.insert(2u32);
568 assert_eq!(res.get::<u32>(), Some(&2));
569 }
570
571 #[test]
572 fn test_resource_get_mut() {
573 let mut res = RuntimeResources::new();
574 res.insert(0u32);
575 *res.get_mut::<u32>().unwrap() = 99;
576 assert_eq!(res.get::<u32>(), Some(&99));
577 }
578
579 #[test]
580 fn test_resource_remove() {
581 let mut res = RuntimeResources::new();
582 res.insert(7u32);
583 assert!(res.contains::<u32>());
584 let val = res.remove::<u32>();
585 assert_eq!(val, Some(7));
586 assert!(!res.contains::<u32>());
587 assert!(res.remove::<u32>().is_none());
589 }
590
591 #[test]
592 fn test_resource_missing_returns_none() {
593 let res = RuntimeResources::new();
594 assert!(res.get::<u32>().is_none());
595 assert!(!res.contains::<u32>());
596 }
597
598 struct CounterWriter {
600 value: u32,
601 }
602
603 impl RuntimePlugin for CounterWriter {
604 fn priority(&self) -> i32 { phase::ANIMATE }
605 fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
606 ctx.resources.insert(self.value);
607 }
608 }
609
610 struct CounterReader {
612 recorded: Arc<Mutex<Vec<u32>>>,
613 }
614
615 impl RuntimePlugin for CounterReader {
616 fn priority(&self) -> i32 { phase::POST_SIM }
617 fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
618 if let Some(&v) = ctx.resources.get::<u32>() {
619 self.recorded.lock().unwrap().push(v);
620 }
621 }
622 }
623
624 #[test]
625 fn test_resource_shared_across_plugins_same_frame() {
626 let recorded = Arc::new(Mutex::new(Vec::<u32>::new()));
629 let mut runtime = ViewportRuntime::new()
630 .with_plugin(CounterWriter { value: 42 })
631 .with_plugin(CounterReader { recorded: recorded.clone() });
632
633 let camera = Camera::default();
634 let input = ActionFrame::default();
635 let mut scene = Scene::new();
636 let mut sel = Selection::new();
637 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
638
639 let vals = recorded.lock().unwrap();
640 assert_eq!(vals.as_slice(), &[42], "reader must see value written by writer in the same frame");
641 }
642
643 #[test]
644 fn test_resource_persists_across_frames() {
645 let mut runtime = ViewportRuntime::new()
647 .with_plugin(CounterWriter { value: 10 });
648
649 let camera = Camera::default();
650 let input = ActionFrame::default();
651 let mut scene = Scene::new();
652 let mut sel = Selection::new();
653
654 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
655 assert!(runtime.resources().contains::<u32>(), "resource must persist after step");
656 }
657
658 #[test]
659 fn test_runtime_works_without_resources() {
660 let mut runtime = ViewportRuntime::new();
662 let camera = Camera::default();
663 let input = ActionFrame::default();
664 let mut scene = Scene::new();
665 let mut sel = Selection::new();
666 let output = runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0));
667 assert!(output.contact_events.is_empty());
668 assert!(output.node_transform_ops.is_empty());
669 }
670
671 #[derive(Debug, PartialEq)]
675 struct GameplayEvent { id: u32 }
676
677 #[derive(Debug, PartialEq)]
678 struct DiagnosticsEvent { frame_ms: f32 }
679
680 struct GameplayEmitter { count: u32 }
682
683 impl RuntimePlugin for GameplayEmitter {
684 fn priority(&self) -> i32 { phase::SIMULATE }
685 fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
686 for i in 0..self.count {
687 ctx.output.events.emit(GameplayEvent { id: i });
688 }
689 }
690 }
691
692 struct DiagnosticsEmitter;
694
695 impl RuntimePlugin for DiagnosticsEmitter {
696 fn priority(&self) -> i32 { phase::POST_SIM }
697 fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
698 ctx.output.events.emit(DiagnosticsEvent { frame_ms: ctx.dt * 1000.0 });
699 }
700 }
701
702 struct GameplayReader {
704 seen: Arc<Mutex<Vec<u32>>>,
705 }
706
707 impl RuntimePlugin for GameplayReader {
708 fn priority(&self) -> i32 { phase::WRITEBACK }
709 fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
710 for ev in ctx.output.events.read::<GameplayEvent>() {
711 self.seen.lock().unwrap().push(ev.id);
712 }
713 }
714 }
715
716 fn run_one_frame(runtime: &mut ViewportRuntime) -> RuntimeOutput {
717 let camera = Camera::default();
718 let input = ActionFrame::default();
719 let mut scene = Scene::new();
720 let mut sel = Selection::new();
721 runtime.step(&mut scene, &mut sel, &make_frame(&camera, &input, 1.0 / 60.0))
722 }
723
724 #[test]
725 fn test_event_bus_emit_and_read() {
726 let mut bus = RuntimeEventBus::new();
727 bus.emit(GameplayEvent { id: 1 });
728 bus.emit(GameplayEvent { id: 2 });
729 let ids: Vec<u32> = bus.read::<GameplayEvent>().map(|e| e.id).collect();
730 assert_eq!(ids, vec![1, 2]);
731 }
732
733 #[test]
734 fn test_event_bus_typed_isolation() {
735 let mut bus = RuntimeEventBus::new();
737 bus.emit(GameplayEvent { id: 42 });
738 assert!(!bus.has::<DiagnosticsEvent>());
739 assert_eq!(bus.count::<DiagnosticsEvent>(), 0);
740 let diag: Vec<_> = bus.read::<DiagnosticsEvent>().collect();
741 assert!(diag.is_empty());
742 }
743
744 #[test]
745 fn test_event_bus_drain() {
746 let mut bus = RuntimeEventBus::new();
747 bus.emit(GameplayEvent { id: 7 });
748 bus.emit(GameplayEvent { id: 8 });
749 let drained = bus.drain::<GameplayEvent>();
750 assert_eq!(drained, vec![GameplayEvent { id: 7 }, GameplayEvent { id: 8 }]);
751 assert!(bus.drain::<GameplayEvent>().is_empty());
753 assert!(!bus.has::<GameplayEvent>());
754 }
755
756 #[test]
757 fn test_event_bus_has_and_count() {
758 let mut bus = RuntimeEventBus::new();
759 assert!(!bus.has::<GameplayEvent>());
760 assert_eq!(bus.count::<GameplayEvent>(), 0);
761 bus.emit(GameplayEvent { id: 1 });
762 bus.emit(GameplayEvent { id: 2 });
763 assert!(bus.has::<GameplayEvent>());
764 assert_eq!(bus.count::<GameplayEvent>(), 2);
765 }
766
767 #[test]
768 fn test_event_bus_is_empty() {
769 let mut bus = RuntimeEventBus::new();
770 assert!(bus.is_empty());
771 bus.emit(GameplayEvent { id: 0 });
772 assert!(!bus.is_empty());
773 }
774
775 #[test]
776 fn test_events_emitted_by_plugin_visible_in_output() {
777 let mut runtime = ViewportRuntime::new()
778 .with_plugin(GameplayEmitter { count: 3 });
779 let output = run_one_frame(&mut runtime);
780 assert_eq!(output.events.count::<GameplayEvent>(), 3);
781 let ids: Vec<u32> = output.events.read::<GameplayEvent>().map(|e| e.id).collect();
782 assert_eq!(ids, vec![0, 1, 2]);
783 }
784
785 #[test]
786 fn test_events_typed_routing_across_plugins() {
787 let seen = Arc::new(Mutex::new(Vec::<u32>::new()));
790 let mut runtime = ViewportRuntime::new()
791 .with_plugin(GameplayEmitter { count: 2 })
792 .with_plugin(GameplayReader { seen: seen.clone() });
793 run_one_frame(&mut runtime);
794 let ids = seen.lock().unwrap();
795 assert_eq!(ids.as_slice(), &[0, 1]);
796 }
797
798 #[test]
799 fn test_multiple_event_types_do_not_interfere() {
800 let mut runtime = ViewportRuntime::new()
802 .with_plugin(GameplayEmitter { count: 2 })
803 .with_plugin(DiagnosticsEmitter);
804 let output = run_one_frame(&mut runtime);
805 assert_eq!(output.events.count::<GameplayEvent>(), 2);
806 assert_eq!(output.events.count::<DiagnosticsEvent>(), 1);
807 }
808
809 #[test]
810 fn test_events_cleared_each_frame() {
811 let mut runtime = ViewportRuntime::new()
813 .with_plugin(GameplayEmitter { count: 1 });
814 run_one_frame(&mut runtime);
815 let output2 = run_one_frame(&mut runtime);
816 assert_eq!(output2.events.count::<GameplayEvent>(), 1);
818 }
819
820 #[test]
821 fn test_existing_output_fields_unaffected() {
822 let mut runtime = ViewportRuntime::new();
825 let output = run_one_frame(&mut runtime);
826 assert!(output.contact_events.is_empty());
827 assert!(output.selection_ops.is_empty());
828 assert!(output.node_transform_ops.is_empty());
829 assert!(output.camera_follow_target.is_none());
830 assert!(output.events.is_empty());
831 }
832
833 use crate::runtime::jobs::{JobPoll, JobSlot};
836
837 #[test]
838 fn test_job_slot_empty_by_default() {
839 let slot: JobSlot<u32> = JobSlot::empty();
840 assert!(slot.is_empty());
841 assert!(!slot.is_pending());
842 assert!(!slot.is_ready());
843 }
844
845 #[test]
846 fn test_job_slot_pending_after_new() {
847 let (slot, _sender) = JobSlot::<u32>::new();
848 assert!(slot.is_pending());
849 assert!(!slot.is_empty());
850 assert!(!slot.is_ready());
851 }
852
853 #[test]
854 fn test_job_handoff_complete() {
855 let (mut slot, sender) = JobSlot::<u32>::new();
856 assert!(slot.is_pending());
857 sender.complete(42);
858 assert!(slot.is_ready());
859 match slot.take() {
860 JobPoll::Ready(v) => assert_eq!(v, 42),
861 _ => panic!("expected Ready, got other variant"),
862 }
863 assert!(slot.is_empty());
865 }
866
867 #[test]
868 fn test_job_handoff_fail() {
869 let (mut slot, sender) = JobSlot::<u32>::new();
870 sender.fail("disk error");
871 match slot.take() {
872 JobPoll::Failed(msg) => assert_eq!(msg, "disk error"),
873 _ => panic!("expected Failed"),
874 }
875 assert!(slot.is_empty());
876 }
877
878 #[test]
879 fn test_job_handoff_cancel() {
880 let (mut slot, sender) = JobSlot::<u32>::new();
881 sender.cancel();
882 match slot.take() {
883 JobPoll::Cancelled => {}
884 _ => panic!("expected Cancelled"),
885 }
886 assert!(slot.is_empty());
887 }
888
889 #[test]
890 fn test_job_sender_drop_cancels() {
891 let (mut slot, sender) = JobSlot::<u32>::new();
892 drop(sender);
893 match slot.take() {
894 JobPoll::Cancelled => {}
895 _ => panic!("expected Cancelled after sender drop"),
896 }
897 assert!(slot.is_empty());
898 }
899
900 #[test]
901 fn test_job_take_on_empty_returns_empty() {
902 let mut slot: JobSlot<u32> = JobSlot::empty();
903 match slot.take() {
904 JobPoll::Empty => {}
905 _ => panic!("expected Empty"),
906 }
907 }
908
909 #[test]
910 fn test_job_take_while_pending_returns_pending() {
911 let (mut slot, _sender) = JobSlot::<u32>::new();
912 match slot.take() {
913 JobPoll::Pending => {}
914 _ => panic!("expected Pending"),
915 }
916 assert!(slot.is_pending());
918 }
919
920 #[test]
921 fn test_job_async_handoff_across_frames() {
922 struct LoaderPlugin {
924 slot: JobSlot<u32>,
925 result: Arc<Mutex<Option<u32>>>,
926 }
927
928 impl RuntimePlugin for LoaderPlugin {
929 fn priority(&self) -> i32 { phase::PREPARE }
930
931 fn submit(&mut self, _ctx: &RuntimeStepContext<'_>) {
932 if self.slot.is_empty() {
933 let (new_slot, sender) = JobSlot::new();
934 self.slot = new_slot;
935 sender.complete(99);
937 }
938 }
939
940 fn collect(&mut self, _ctx: &mut RuntimeStepContext<'_>) {
941 if let JobPoll::Ready(v) = self.slot.take() {
942 *self.result.lock().unwrap() = Some(v);
943 }
944 }
945
946 fn step(&mut self, _ctx: &mut RuntimeStepContext<'_>) {}
947 }
948
949 let result = Arc::new(Mutex::new(None::<u32>));
950 let mut runtime = ViewportRuntime::new()
951 .with_plugin(LoaderPlugin { slot: JobSlot::empty(), result: result.clone() });
952
953 run_one_frame(&mut runtime);
954 assert_eq!(*result.lock().unwrap(), Some(99));
955 }
956
957 #[test]
958 fn test_job_staged_resource_update_ordering() {
959 struct Integrator {
964 slot: JobSlot<u32>,
965 }
966
967 impl RuntimePlugin for Integrator {
968 fn priority(&self) -> i32 { phase::PREPARE } fn submit(&mut self, _ctx: &RuntimeStepContext<'_>) {
971 if self.slot.is_empty() {
972 let (s, sender) = JobSlot::new();
973 self.slot = s;
974 sender.complete(77);
975 }
976 }
977
978 fn collect(&mut self, ctx: &mut RuntimeStepContext<'_>) {
979 if let JobPoll::Ready(v) = self.slot.take() {
980 ctx.resources.insert(v);
981 }
982 }
983
984 fn step(&mut self, _ctx: &mut RuntimeStepContext<'_>) {}
985 }
986
987 struct Reader {
988 seen: Arc<Mutex<Option<u32>>>,
989 }
990
991 impl RuntimePlugin for Reader {
992 fn priority(&self) -> i32 { phase::POST_SIM } fn collect(&mut self, ctx: &mut RuntimeStepContext<'_>) {
995 *self.seen.lock().unwrap() = ctx.resources.get::<u32>().copied();
996 }
997
998 fn step(&mut self, _ctx: &mut RuntimeStepContext<'_>) {}
999 }
1000
1001 let seen = Arc::new(Mutex::new(None::<u32>));
1002 let mut runtime = ViewportRuntime::new()
1003 .with_plugin(Integrator { slot: JobSlot::empty() })
1004 .with_plugin(Reader { seen: seen.clone() });
1005
1006 run_one_frame(&mut runtime);
1007 assert_eq!(*seen.lock().unwrap(), Some(77));
1009 }
1010
1011 #[test]
1012 fn test_job_slot_in_resources() {
1013 let (slot, sender) = JobSlot::<u32>::new();
1015 let mut res = RuntimeResources::new();
1016 res.insert(slot);
1017 sender.complete(55);
1018 let found = res.get_mut::<JobSlot<u32>>().unwrap();
1019 match found.take() {
1020 JobPoll::Ready(v) => assert_eq!(v, 55),
1021 _ => panic!("expected Ready"),
1022 }
1023 }
1024
1025 use crate::resources::SkinWeights;
1028 use crate::runtime::plugins::skeleton_plugin::{
1029 Joint, JointMatrices, Pose, Skeleton, SkeletonPlugin, apply_skin,
1030 };
1031
1032 fn two_joint_skeleton() -> Skeleton {
1033 Skeleton::new(vec![
1034 Joint {
1035 name: "root".into(),
1036 parent: None,
1037 inverse_bind: glam::Affine3A::IDENTITY,
1038 },
1039 Joint {
1040 name: "child".into(),
1041 parent: Some(0),
1042 inverse_bind: glam::Affine3A::from_translation(-glam::Vec3::Y),
1043 },
1044 ])
1045 }
1046
1047 fn single_vertex_weights(joint: u8, weight: f32) -> SkinWeights {
1048 SkinWeights {
1049 joint_indices: vec![[joint, 0, 0, 0]],
1050 joint_weights: vec![[weight, 0.0, 0.0, 0.0]],
1051 }
1052 }
1053
1054 #[test]
1055 fn test_skeleton_joint_count() {
1056 let sk = two_joint_skeleton();
1057 assert_eq!(sk.joint_count(), 2);
1058 }
1059
1060 #[test]
1061 fn test_skeleton_find_joint() {
1062 let sk = two_joint_skeleton();
1063 assert_eq!(sk.find_joint("root"), Some(0));
1064 assert_eq!(sk.find_joint("child"), Some(1));
1065 assert_eq!(sk.find_joint("missing"), None);
1066 }
1067
1068 #[test]
1069 fn test_pose_identity_transforms() {
1070 let pose = Pose::identity(3);
1071 assert_eq!(pose.joint_count(), 3);
1072 for t in &pose.local_transforms {
1073 assert_eq!(*t, glam::Affine3A::IDENTITY);
1074 }
1075 }
1076
1077 #[test]
1078 fn test_joint_matrices_single_root_identity() {
1079 let sk = Skeleton::new(vec![Joint {
1080 name: "root".into(),
1081 parent: None,
1082 inverse_bind: glam::Affine3A::IDENTITY,
1083 }]);
1084 let pose = Pose::identity(1);
1085 let mats = JointMatrices::compute(&sk, &pose);
1086 let m = mats.as_slice()[0];
1087 assert!(m.matrix3.col(0).abs_diff_eq(glam::Vec3A::X, 1e-5));
1089 assert!(m.translation.abs_diff_eq(glam::Vec3A::ZERO, 1e-5));
1090 }
1091
1092 #[test]
1093 fn test_joint_matrices_parent_child_chain() {
1094 let sk = two_joint_skeleton();
1100 let mut pose = Pose::identity(2);
1101 pose.local_transforms[0] = glam::Affine3A::from_translation(glam::Vec3::X);
1102 let mats = JointMatrices::compute(&sk, &pose);
1103 let child = mats.as_slice()[1];
1104 let expected_translation = glam::Vec3A::new(1.0, -1.0, 0.0);
1105 assert!(
1106 child.translation.abs_diff_eq(expected_translation, 1e-5),
1107 "child translation was {:?}, expected {:?}",
1108 child.translation,
1109 expected_translation,
1110 );
1111 }
1112
1113 #[test]
1114 fn test_apply_skin_identity_pose() {
1115 let sk = Skeleton::new(vec![Joint {
1116 name: "root".into(),
1117 parent: None,
1118 inverse_bind: glam::Affine3A::IDENTITY,
1119 }]);
1120 let pose = Pose::identity(1);
1121 let mats = JointMatrices::compute(&sk, &pose);
1122
1123 let positions = vec![[1.0f32, 2.0, 3.0]];
1124 let normals = vec![[0.0f32, 1.0, 0.0]];
1125 let weights = single_vertex_weights(0, 1.0);
1126 let (out_pos, out_nrm) = apply_skin(&positions, &normals, &weights, &mats);
1127
1128 assert!((out_pos[0][0] - 1.0).abs() < 1e-5);
1129 assert!((out_pos[0][1] - 2.0).abs() < 1e-5);
1130 assert!((out_pos[0][2] - 3.0).abs() < 1e-5);
1131 assert!((out_nrm[0][1] - 1.0).abs() < 1e-5);
1132 }
1133
1134 #[test]
1135 fn test_apply_skin_single_joint_full_weight() {
1136 let sk = Skeleton::new(vec![Joint {
1138 name: "root".into(),
1139 parent: None,
1140 inverse_bind: glam::Affine3A::IDENTITY,
1141 }]);
1142 let mut pose = Pose::identity(1);
1143 pose.local_transforms[0] = glam::Affine3A::from_translation(glam::Vec3::new(5.0, 0.0, 0.0));
1144 let mats = JointMatrices::compute(&sk, &pose);
1145
1146 let positions = vec![[0.0f32, 0.0, 0.0]];
1147 let normals = vec![[1.0f32, 0.0, 0.0]];
1148 let weights = single_vertex_weights(0, 1.0);
1149 let (out_pos, _) = apply_skin(&positions, &normals, &weights, &mats);
1150 assert!((out_pos[0][0] - 5.0).abs() < 1e-5);
1151 assert!(out_pos[0][1].abs() < 1e-5);
1152 }
1153
1154 #[test]
1155 fn test_apply_skin_two_joint_blend() {
1156 let sk = Skeleton::new(vec![
1159 Joint { name: "a".into(), parent: None, inverse_bind: glam::Affine3A::IDENTITY },
1160 Joint { name: "b".into(), parent: None, inverse_bind: glam::Affine3A::IDENTITY },
1161 ]);
1162 let mut pose = Pose::identity(2);
1163 pose.local_transforms[1] = glam::Affine3A::from_translation(glam::Vec3::new(10.0, 0.0, 0.0));
1164 let mats = JointMatrices::compute(&sk, &pose);
1165
1166 let positions = vec![[0.0f32, 0.0, 0.0]];
1167 let normals = vec![[1.0f32, 0.0, 0.0]];
1168 let weights = SkinWeights {
1169 joint_indices: vec![[0, 1, 0, 0]],
1170 joint_weights: vec![[0.5, 0.5, 0.0, 0.0]],
1171 };
1172 let (out_pos, _) = apply_skin(&positions, &normals, &weights, &mats);
1173 assert!((out_pos[0][0] - 5.0).abs() < 1e-5, "expected 5.0, got {}", out_pos[0][0]);
1174 }
1175
1176 #[test]
1177 fn test_apply_skin_normal_renormalized() {
1178 let sk = Skeleton::new(vec![Joint {
1180 name: "root".into(),
1181 parent: None,
1182 inverse_bind: glam::Affine3A::IDENTITY,
1183 }]);
1184 let mut pose = Pose::identity(1);
1185 pose.local_transforms[0] = glam::Affine3A::from_scale(glam::Vec3::splat(2.0));
1186 let mats = JointMatrices::compute(&sk, &pose);
1187
1188 let positions = vec![[1.0f32, 0.0, 0.0]];
1189 let normals = vec![[1.0f32, 0.0, 0.0]];
1190 let weights = single_vertex_weights(0, 1.0);
1191 let (_, out_nrm) = apply_skin(&positions, &normals, &weights, &mats);
1192 let len = (out_nrm[0][0].powi(2) + out_nrm[0][1].powi(2) + out_nrm[0][2].powi(2)).sqrt();
1193 assert!((len - 1.0).abs() < 1e-5, "normal length was {len}");
1194 }
1195
1196 #[test]
1197 fn test_skeleton_plugin_writes_update_to_output() {
1198 let sk = Skeleton::new(vec![Joint {
1199 name: "root".into(),
1200 parent: None,
1201 inverse_bind: glam::Affine3A::IDENTITY,
1202 }]);
1203 let positions = vec![[0.0f32, 0.0, 0.0]];
1204 let normals = vec![[0.0f32, 1.0, 0.0]];
1205 let weights = single_vertex_weights(0, 1.0);
1206 let mesh_id = crate::resources::mesh_store::MeshId(0);
1207
1208 let mut runtime = ViewportRuntime::new()
1209 .with_plugin(SkeletonPlugin::new(sk, mesh_id, positions, normals, weights));
1210
1211 runtime.resources_mut().insert(Pose::identity(1));
1213
1214 let output = run_one_frame(&mut runtime);
1215 assert_eq!(output.skinned_mesh_updates.len(), 1);
1216 assert_eq!(output.skinned_mesh_updates[0].mesh_id, mesh_id);
1217 }
1218
1219 #[test]
1220 fn test_skinned_mesh_updates_empty_without_pose() {
1221 let sk = Skeleton::new(vec![Joint {
1222 name: "root".into(),
1223 parent: None,
1224 inverse_bind: glam::Affine3A::IDENTITY,
1225 }]);
1226 let mesh_id = crate::resources::mesh_store::MeshId(0);
1227 let mut runtime = ViewportRuntime::new()
1228 .with_plugin(SkeletonPlugin::new(
1229 sk,
1230 mesh_id,
1231 vec![[0.0f32; 3]],
1232 vec![[0.0f32, 1.0, 0.0]],
1233 single_vertex_weights(0, 1.0),
1234 ));
1235 let output = run_one_frame(&mut runtime);
1237 assert!(output.skinned_mesh_updates.is_empty());
1238 }
1239
1240 #[test]
1241 fn test_skinned_mesh_updates_cleared_each_frame() {
1242 let sk = Skeleton::new(vec![Joint {
1243 name: "root".into(),
1244 parent: None,
1245 inverse_bind: glam::Affine3A::IDENTITY,
1246 }]);
1247 let mesh_id = crate::resources::mesh_store::MeshId(0);
1248 let mut runtime = ViewportRuntime::new()
1249 .with_plugin(SkeletonPlugin::new(
1250 sk,
1251 mesh_id,
1252 vec![[0.0f32; 3]],
1253 vec![[0.0f32, 1.0, 0.0]],
1254 single_vertex_weights(0, 1.0),
1255 ));
1256 runtime.resources_mut().insert(Pose::identity(1));
1257
1258 run_one_frame(&mut runtime);
1259 let output2 = run_one_frame(&mut runtime);
1260 assert_eq!(output2.skinned_mesh_updates.len(), 1);
1262 }
1263}
1264
1265pub struct ViewportRuntime {
1280 mode: SceneRuntimeMode,
1281 plugins: Vec<Box<dyn RuntimePlugin>>,
1282 fixed_timestep: Option<FixedTimestep>,
1283 snapshots: TransformSnapshotTable,
1284 step_index: u64,
1285 selection_system: Option<SelectionSystem>,
1286 manipulation_system: Option<ManipulationSystem>,
1287 camera_follow: Option<CameraFollow>,
1288 resources: RuntimeResources,
1290 prev_node_ids: std::collections::HashSet<crate::interaction::selection::NodeId>,
1292 scene_initialized: bool,
1294}
1295
1296impl Default for ViewportRuntime {
1297 fn default() -> Self {
1298 Self::new()
1299 }
1300}
1301
1302impl ViewportRuntime {
1303 pub fn new() -> Self {
1305 Self {
1306 mode: SceneRuntimeMode::default(),
1307 plugins: Vec::new(),
1308 fixed_timestep: None,
1309 snapshots: TransformSnapshotTable::new(),
1310 step_index: 0,
1311 selection_system: None,
1312 manipulation_system: None,
1313 camera_follow: None,
1314 resources: RuntimeResources::new(),
1315 prev_node_ids: std::collections::HashSet::new(),
1316 scene_initialized: false,
1317 }
1318 }
1319
1320 pub fn with_selection_system(mut self) -> Self {
1326 self.selection_system = Some(SelectionSystem::new());
1327 self
1328 }
1329
1330 pub fn with_manipulation_system(mut self) -> Self {
1336 self.manipulation_system = Some(ManipulationSystem::new());
1337 self
1338 }
1339
1340 pub fn is_manipulating(&self) -> bool {
1344 self.manipulation_system
1345 .as_ref()
1346 .map_or(false, |m| m.is_active())
1347 }
1348
1349 pub fn manipulation_system(&self) -> Option<&ManipulationSystem> {
1351 self.manipulation_system.as_ref()
1352 }
1353
1354 pub fn with_mode(mut self, mode: SceneRuntimeMode) -> Self {
1356 self.mode = mode;
1357 self
1358 }
1359
1360 pub fn with_plugin(mut self, plugin: impl RuntimePlugin) -> Self {
1363 self.plugins.push(Box::new(plugin));
1364 self
1365 }
1366
1367 pub fn with_fixed_timestep(mut self, ts: FixedTimestep) -> Self {
1373 self.fixed_timestep = Some(ts);
1374 self
1375 }
1376
1377 pub fn set_fixed_timestep(&mut self, ts: FixedTimestep) {
1382 self.fixed_timestep = Some(ts);
1383 }
1384
1385 pub fn clear_fixed_timestep(&mut self) {
1387 self.fixed_timestep = None;
1388 }
1389
1390 pub fn mode(&self) -> SceneRuntimeMode {
1392 self.mode
1393 }
1394
1395 pub fn snapshots(&self) -> &TransformSnapshotTable {
1400 &self.snapshots
1401 }
1402
1403 pub fn resources(&self) -> &RuntimeResources {
1408 &self.resources
1409 }
1410
1411 pub fn resources_mut(&mut self) -> &mut RuntimeResources {
1416 &mut self.resources
1417 }
1418
1419 pub fn alpha(&self) -> f32 {
1424 self.fixed_timestep.as_ref().map_or(1.0, |ts| ts.alpha())
1425 }
1426
1427 pub fn step_index(&self) -> u64 {
1432 self.step_index
1433 }
1434
1435 pub fn with_camera_follow(mut self, follow: CameraFollow) -> Self {
1440 self.camera_follow = Some(follow);
1441 self
1442 }
1443
1444 pub fn set_camera_follow(&mut self, follow: CameraFollow) {
1446 self.camera_follow = Some(follow);
1447 }
1448
1449 pub fn clear_camera_follow(&mut self) {
1452 self.camera_follow = None;
1453 }
1454
1455 pub fn camera_follow(&self) -> Option<&CameraFollow> {
1457 self.camera_follow.as_ref()
1458 }
1459
1460 pub fn step(
1471 &mut self,
1472 scene: &mut Scene,
1473 selection: &mut Selection,
1474 frame: &RuntimeFrameContext,
1475 ) -> RuntimeOutput {
1476 let mut output = RuntimeOutput::default();
1477 let mut writeback = TransformWriteback::default();
1478
1479 let current_ids: std::collections::HashSet<crate::interaction::selection::NodeId> =
1482 scene.nodes().map(|n| n.id()).collect();
1483
1484 if self.scene_initialized {
1485 let mut events: Vec<RuntimeEvent> = Vec::new();
1487 for &id in current_ids.difference(&self.prev_node_ids) {
1488 events.push(RuntimeEvent::NodeAdded(id));
1489 }
1490 for &id in self.prev_node_ids.difference(¤t_ids) {
1491 events.push(RuntimeEvent::NodeRemoved(id));
1492 }
1493
1494 if !events.is_empty() {
1495 let mut plugins = std::mem::take(&mut self.plugins);
1497 for event in &events {
1498 for plugin in plugins.iter_mut() {
1499 let mut ctx = RuntimeStepContext {
1500 priority: plugin.priority(),
1501 dt: frame.dt,
1502 scene,
1503 writeback: &mut writeback,
1504 output: &mut output,
1505 pick_hit: frame.pick_hit,
1506 resources: &mut self.resources,
1507 };
1508 plugin.on_event(event, &mut ctx);
1509 }
1510 }
1511 self.plugins = plugins;
1512 }
1513 } else {
1514 self.scene_initialized = true;
1515 }
1516 self.prev_node_ids = current_ids;
1517
1518 self.plugins.sort_by_key(|p| p.priority());
1520
1521 let mut plugins = std::mem::take(&mut self.plugins);
1523
1524 for plugin in plugins.iter_mut() {
1526 let ctx = RuntimeStepContext {
1527 priority: plugin.priority(),
1528 dt: frame.dt,
1529 scene,
1530 writeback: &mut writeback,
1531 output: &mut output,
1532 pick_hit: frame.pick_hit,
1533 resources: &mut self.resources,
1534 };
1535 plugin.submit(&ctx);
1536 }
1537
1538 run_range(
1542 &mut plugins,
1543 i32::MIN,
1544 phase::SELECT,
1545 frame.dt,
1546 frame,
1547 scene,
1548 &mut writeback,
1549 &mut output,
1550 &mut self.resources,
1551 );
1552
1553 if let Some(sel_sys) = &self.selection_system {
1555 sel_sys.step(frame, &mut output);
1556 }
1557 run_range(
1559 &mut plugins,
1560 phase::SELECT,
1561 phase::MANIPULATE,
1562 frame.dt,
1563 frame,
1564 scene,
1565 &mut writeback,
1566 &mut output,
1567 &mut self.resources,
1568 );
1569
1570 if self.manipulation_system.is_some() {
1572 let mut manip_sys = self.manipulation_system.take().unwrap();
1573 manip_sys.step(frame, scene, selection, &mut writeback, &mut output);
1574 self.manipulation_system = Some(manip_sys);
1575 }
1576 run_range(
1578 &mut plugins,
1579 phase::MANIPULATE,
1580 phase::SIMULATE,
1581 frame.dt,
1582 frame,
1583 scene,
1584 &mut writeback,
1585 &mut output,
1586 &mut self.resources,
1587 );
1588
1589 if self.fixed_timestep.is_some() {
1591 let mut ts = self.fixed_timestep.take().unwrap();
1592 for step_dt in ts.advance(frame.dt) {
1593 run_range(
1594 &mut plugins,
1595 phase::SIMULATE,
1596 phase::POST_SIM,
1597 step_dt,
1598 frame,
1599 scene,
1600 &mut writeback,
1601 &mut output,
1602 &mut self.resources,
1603 );
1604 self.step_index = self.step_index.wrapping_add(1);
1605 }
1606 self.fixed_timestep = Some(ts);
1607 } else {
1608 run_range(
1609 &mut plugins,
1610 phase::SIMULATE,
1611 phase::POST_SIM,
1612 frame.dt,
1613 frame,
1614 scene,
1615 &mut writeback,
1616 &mut output,
1617 &mut self.resources,
1618 );
1619 self.step_index = self.step_index.wrapping_add(1);
1620 }
1621
1622 run_range(
1624 &mut plugins,
1625 phase::POST_SIM,
1626 i32::MAX,
1627 frame.dt,
1628 frame,
1629 scene,
1630 &mut writeback,
1631 &mut output,
1632 &mut self.resources,
1633 );
1634
1635 for plugin in plugins.iter_mut() {
1637 let mut ctx = RuntimeStepContext {
1638 priority: plugin.priority(),
1639 dt: frame.dt,
1640 scene,
1641 writeback: &mut writeback,
1642 output: &mut output,
1643 pick_hit: frame.pick_hit,
1644 resources: &mut self.resources,
1645 };
1646 plugin.collect(&mut ctx);
1647 }
1648
1649 self.plugins = plugins;
1651
1652 let ops = writeback.into_ops();
1654 for op in &ops {
1655 scene.set_local_transform(op.id, glam::Mat4::from(op.transform));
1656 self.snapshots.update(op.id, op.transform);
1657 }
1658 if !ops.is_empty() {
1659 scene.update_transforms();
1660 }
1661 output.node_transform_ops = ops;
1662
1663 for op in &output.selection_ops {
1665 op.apply_to(selection);
1666 }
1667
1668 if let Some(CameraFollow::Node { id, offset, .. }) = &self.camera_follow {
1670 let id = *id;
1671 let offset = *offset;
1672 let alpha = self.alpha();
1673 let pos = self
1674 .snapshots
1675 .interpolated(id, alpha)
1676 .map(|t| glam::Vec3::from(t.translation))
1677 .or_else(|| {
1678 scene
1679 .node(id)
1680 .map(|n| n.world_transform().col(3).truncate())
1681 });
1682 if let Some(pos) = pos {
1683 output.camera_follow_target = Some(pos + offset);
1684 }
1685 }
1686
1687 output
1688 }
1689}