1use super::protocol::{Driver, Easing};
10
11const SPRING_REST_DELTA: f32 = 0.01;
12const SPRING_REST_SPEED: f32 = 0.01;
13const SPRING_SUBSTEP: f32 = 0.001;
14const SPRING_MAX_SUBSTEPS: u32 = 64;
15
16fn ease(easing: Easing, t: f32) -> f32 {
17 let t = t.clamp(0.0, 1.0);
18 match easing {
19 Easing::Linear => t,
20 Easing::EaseIn => t * t * t,
21 Easing::EaseOut => {
22 let u = 1.0 - t;
23 1.0 - u * u * u
24 }
25 Easing::EaseInOut => {
26 if t < 0.5 {
27 4.0 * t * t * t
28 } else {
29 let u = -2.0 * t + 2.0;
30 1.0 - u * u * u / 2.0
31 }
32 }
33 }
34}
35
36pub enum Runner {
43 Done(f32),
45 Timing {
46 from: f32,
47 to: f32,
48 duration: f32,
49 easing: Easing,
50 elapsed: f32,
51 },
52 Spring {
53 to: f32,
54 stiffness: f32,
55 damping: f32,
56 mass: f32,
57 pos: f32,
58 vel: f32,
59 },
60 Repeat {
61 template: Box<Driver>,
62 remaining: i32,
63 reverse: bool,
64 iteration: u32,
65 a: f32,
66 b: f32,
67 child: Box<Runner>,
68 },
69 Sequence {
70 steps: Vec<Driver>,
71 index: usize,
72 child: Box<Runner>,
73 },
74 Delay {
75 remaining: f32,
76 animation: Box<Driver>,
77 from: f32,
78 child: Option<Box<Runner>>,
79 },
80}
81
82pub fn build_runner(driver: &Driver, from: f32) -> Runner {
85 build_runner_with_target(driver, from, None)
86}
87
88fn build_runner_with_target(driver: &Driver, from: f32, target: Option<f32>) -> Runner {
91 match driver {
92 Driver::Timing {
93 to,
94 duration,
95 easing,
96 } => Runner::Timing {
97 from,
98 to: target.unwrap_or(*to),
99 duration: duration.max(0.0),
100 easing: *easing,
101 elapsed: 0.0,
102 },
103 Driver::Spring {
104 to,
105 stiffness,
106 damping,
107 mass,
108 } => Runner::Spring {
109 to: target.unwrap_or(*to),
110 stiffness: stiffness.max(1e-4),
115 damping: damping.max(1e-4),
116 mass: mass.max(1e-4),
117 pos: from,
118 vel: 0.0,
119 },
120 Driver::Repeat {
121 animation,
122 count,
123 reverse,
124 } => {
125 if *count == 0 {
126 return Runner::Done(from);
127 }
128 Runner::Repeat {
129 template: animation.clone(),
130 remaining: *count,
131 reverse: *reverse,
132 iteration: 0,
133 a: from,
134 b: terminal_value(animation, from),
135 child: Box::new(build_runner(animation, from)),
136 }
137 }
138 Driver::Sequence { steps } => {
139 if steps.is_empty() {
140 return Runner::Done(from);
141 }
142 Runner::Sequence {
143 steps: steps.clone(),
144 index: 0,
145 child: Box::new(build_runner(&steps[0], from)),
146 }
147 }
148 Driver::Delay { delay, animation } => Runner::Delay {
149 remaining: delay.max(0.0),
150 animation: animation.clone(),
151 from,
152 child: None,
153 },
154 }
155}
156
157fn terminal_value(driver: &Driver, from: f32) -> f32 {
160 match driver {
161 Driver::Timing { to, .. } => *to,
162 Driver::Spring { to, .. } => *to,
163 Driver::Sequence { steps } => steps.iter().fold(from, |acc, s| terminal_value(s, acc)),
164 Driver::Delay { animation, .. } => terminal_value(animation, from),
165 Driver::Repeat {
166 animation,
167 count,
168 reverse,
169 } => {
170 let a = from;
171 let b = terminal_value(animation, from);
172 if *count <= 0 {
173 b
174 } else if *reverse && count % 2 == 0 {
175 a
176 } else {
177 b
178 }
179 }
180 }
181}
182
183impl Runner {
184 pub fn step(&mut self, dt: f32) -> (f32, bool) {
186 match self {
187 Runner::Done(v) => (*v, true),
188 Runner::Timing {
189 from,
190 to,
191 duration,
192 easing,
193 elapsed,
194 } => {
195 *elapsed += dt;
196 if *duration <= 0.0 {
197 return (*to, true);
198 }
199 let t = (*elapsed / *duration).clamp(0.0, 1.0);
200 let v = *from + (*to - *from) * ease(*easing, t);
201 (v, *elapsed >= *duration)
202 }
203 Runner::Spring {
204 to,
205 stiffness,
206 damping,
207 mass,
208 pos,
209 vel,
210 } => {
211 let n = ((dt / SPRING_SUBSTEP).ceil() as u32).clamp(1, SPRING_MAX_SUBSTEPS);
212 let h = dt / n as f32;
213 for _ in 0..n {
214 let force = -*stiffness * (*pos - *to) - *damping * *vel;
215 let acc = force / *mass;
216 *vel += acc * h;
217 *pos += *vel * h;
218 }
219 let settled =
220 (*pos - *to).abs() < SPRING_REST_DELTA && vel.abs() < SPRING_REST_SPEED;
221 if settled {
222 *pos = *to;
223 *vel = 0.0;
224 }
225 (*pos, settled)
226 }
227 Runner::Repeat {
228 template,
229 remaining,
230 reverse,
231 iteration,
232 a,
233 b,
234 child,
235 } => {
236 let (v, done) = child.step(dt);
237 if !done {
238 return (v, false);
239 }
240 if *remaining > 0 {
241 *remaining -= 1;
242 if *remaining == 0 {
243 return (v, true);
244 }
245 }
246 *iteration += 1;
247 let (from, target) = if *reverse {
248 if *iteration % 2 == 0 {
249 (*a, *b)
250 } else {
251 (*b, *a)
252 }
253 } else {
254 (*a, *b)
255 };
256 **child = build_runner_with_target(template, from, Some(target));
257 (v, false)
258 }
259 Runner::Sequence {
260 steps,
261 index,
262 child,
263 } => {
264 let (v, done) = child.step(dt);
265 if !done {
266 return (v, false);
267 }
268 if *index + 1 >= steps.len() {
269 return (v, true);
270 }
271 *index += 1;
272 **child = build_runner(&steps[*index], v);
273 (v, false)
274 }
275 Runner::Delay {
276 remaining,
277 animation,
278 from,
279 child,
280 } => {
281 if let Some(child) = child {
282 return child.step(dt);
283 }
284 *remaining -= dt;
285 if *remaining > 0.0 {
286 return (*from, false);
287 }
288 let leftover = -*remaining;
291 let mut runner = build_runner(animation, *from);
292 let result = runner.step(leftover);
293 *child = Some(Box::new(runner));
294 result
295 }
296 }
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 fn timing(to: f32, duration: f32) -> Driver {
305 Driver::Timing {
306 to,
307 duration,
308 easing: Easing::Linear,
309 }
310 }
311
312 fn run_to_end(driver: &Driver, from: f32, dt: f32, max_ticks: usize) -> (f32, usize) {
314 let mut r = build_runner(driver, from);
315 for i in 0..max_ticks {
316 let (v, done) = r.step(dt);
317 if done {
318 return (v, i + 1);
319 }
320 }
321 panic!("runner did not finish in {max_ticks} ticks");
322 }
323
324 #[test]
325 fn easing_endpoints_and_midpoint() {
326 for e in [
327 Easing::Linear,
328 Easing::EaseIn,
329 Easing::EaseOut,
330 Easing::EaseInOut,
331 ] {
332 assert!((ease(e, 0.0) - 0.0).abs() < 1e-6, "{e:?} at 0");
333 assert!((ease(e, 1.0) - 1.0).abs() < 1e-6, "{e:?} at 1");
334 }
335 assert!((ease(Easing::Linear, 0.5) - 0.5).abs() < 1e-6);
336 assert!(ease(Easing::EaseIn, 0.5) < 0.5);
338 assert!(ease(Easing::EaseOut, 0.5) > 0.5);
339 }
340
341 #[test]
345 fn spring_survives_hostile_params() {
346 for (stiffness, damping, mass) in [
347 (-1.0, -5.0, 0.0),
348 (0.0, 0.0, -1.0),
349 (f32::NAN, f32::NAN, f32::NAN),
350 ] {
351 let driver = Driver::Spring {
352 to: 100.0,
353 stiffness,
354 damping,
355 mass,
356 };
357 let mut r = build_runner(&driver, 0.0);
358 let mut settled = false;
359 for _ in 0..10_000 {
360 let (v, done) = r.step(1.0 / 60.0);
361 assert!(
362 v.is_finite(),
363 "diverged with k={stiffness} c={damping} m={mass}"
364 );
365 if done {
366 settled = true;
367 break;
368 }
369 }
370 assert!(
371 settled,
372 "never settled with k={stiffness} c={damping} m={mass}"
373 );
374 }
375 }
376
377 #[test]
378 fn timing_runs_from_current_to_target() {
379 let mut r = build_runner(&timing(100.0, 1.0), 0.0);
380 let (v1, done1) = r.step(0.5);
381 assert!(!done1);
382 assert!((v1 - 50.0).abs() < 1e-3, "halfway expected ~50, got {v1}");
383 let (v2, done2) = r.step(0.5);
384 assert!(done2);
385 assert!((v2 - 100.0).abs() < 1e-3, "end expected 100, got {v2}");
386 }
387
388 #[test]
389 fn zero_duration_timing_snaps() {
390 let mut r = build_runner(&timing(42.0, 0.0), 0.0);
391 let (v, done) = r.step(0.016);
392 assert!(done);
393 assert_eq!(v, 42.0);
394 }
395
396 #[test]
397 fn spring_settles_on_target() {
398 let driver = Driver::Spring {
399 to: 100.0,
400 stiffness: 120.0,
401 damping: 14.0,
402 mass: 1.0,
403 };
404 let (v, ticks) = run_to_end(&driver, 0.0, 1.0 / 60.0, 2000);
405 assert!(
406 (v - 100.0).abs() < 0.1,
407 "spring should settle near 100, got {v}"
408 );
409 assert!(ticks > 1, "spring should take multiple ticks");
410 }
411
412 #[test]
413 fn delay_holds_then_runs_child() {
414 let driver = Driver::Delay {
416 delay: 0.5,
417 animation: Box::new(timing(30.0, 1.0)),
418 };
419 let mut r = build_runner(&driver, 10.0);
420 let (v1, d1) = r.step(0.25);
421 assert!(!d1);
422 assert!(
423 (v1 - 10.0).abs() < 1e-6,
424 "still holding, expected 10, got {v1}"
425 );
426 let (v2, d2) = r.step(0.25); assert!(!d2);
428 assert!(
429 (v2 - 10.0).abs() < 1e-3,
430 "child at t=0 expected 10, got {v2}"
431 );
432 let (v3, _d3) = r.step(0.5); assert!((v3 - 20.0).abs() < 1e-3, "halfway expected 20, got {v3}");
434 let (v4, d4) = r.step(0.5);
435 assert!(d4);
436 assert!((v4 - 30.0).abs() < 1e-3, "end expected 30, got {v4}");
437 }
438
439 #[test]
440 fn delay_carries_leftover_time_across_the_boundary() {
441 let driver = Driver::Delay {
444 delay: 0.1,
445 animation: Box::new(timing(100.0, 1.0)),
446 };
447 let mut r = build_runner(&driver, 0.0);
448 let (v, done) = r.step(0.6);
449 assert!(!done);
450 assert!(
451 (v - 50.0).abs() < 1e-3,
452 "expected ~50 after leftover, got {v}"
453 );
454 }
455
456 #[test]
457 fn zero_delay_runs_child_immediately() {
458 let driver = Driver::Delay {
460 delay: 0.0,
461 animation: Box::new(timing(100.0, 1.0)),
462 };
463 let mut r = build_runner(&driver, 0.0);
464 let (v, done) = r.step(0.5);
465 assert!(!done);
466 assert!(
467 (v - 50.0).abs() < 1e-3,
468 "zero delay should not hold, got {v}"
469 );
470 }
471
472 #[test]
473 fn delay_inside_repeated_sequence_composes() {
474 let amp = 100.0;
476 let bounce = Driver::Repeat {
477 animation: Box::new(Driver::Sequence {
478 steps: vec![
479 Driver::Delay {
480 delay: 0.2,
481 animation: Box::new(timing(amp, 0.5)),
482 },
483 Driver::Delay {
484 delay: 0.2,
485 animation: Box::new(timing(-amp, 0.5)),
486 },
487 ],
488 }),
489 count: -1,
490 reverse: false,
491 };
492 let mut r = build_runner(&bounce, -amp);
493 let mut min = f32::INFINITY;
495 let mut max = f32::NEG_INFINITY;
496 for _ in 0..240 {
497 let (v, done) = r.step(1.0 / 60.0);
498 assert!(!done, "infinite bounce must never finish");
499 min = min.min(v);
500 max = max.max(v);
501 }
502 assert!(
503 min <= -amp + 1.0,
504 "should reach the left extreme, min={min}"
505 );
506 assert!(
507 max >= amp - 1.0,
508 "should reach the right extreme, max={max}"
509 );
510 assert!(min >= -amp - 1e-3 && max <= amp + 1e-3, "must stay bounded");
511 }
512
513 #[test]
514 fn sequence_chains_steps_from_previous_end() {
515 let driver = Driver::Sequence {
517 steps: vec![timing(50.0, 1.0), timing(120.0, 1.0)],
518 };
519 let mut r = build_runner(&driver, 0.0);
520 let (v1, d1) = r.step(1.0); assert!(!d1);
522 assert!(
523 (v1 - 50.0).abs() < 1e-3,
524 "after step 1 expected 50, got {v1}"
525 );
526 let (v2, _d2) = r.step(0.5); assert!(
528 (v2 - 85.0).abs() < 1.0,
529 "midway second step expected ~85, got {v2}"
530 );
531 let (v3, d3) = r.step(0.5);
532 assert!(d3);
533 assert!(
534 (v3 - 120.0).abs() < 1e-3,
535 "sequence end expected 120, got {v3}"
536 );
537 }
538
539 #[test]
540 fn finite_repeat_finishes_after_count_cycles() {
541 let driver = Driver::Repeat {
543 animation: Box::new(timing(10.0, 1.0)),
544 count: 2,
545 reverse: false,
546 };
547 let (v, _ticks) = run_to_end(&driver, 0.0, 0.25, 1000);
548 assert!(
549 (v - 10.0).abs() < 1e-3,
550 "finite repeat ends at target, got {v}"
551 );
552 }
553
554 #[test]
555 fn reverse_repeat_ping_pongs_endpoints() {
556 let driver = Driver::Repeat {
558 animation: Box::new(timing(10.0, 1.0)),
559 count: 2,
560 reverse: true,
561 };
562 let (v, _ticks) = run_to_end(&driver, 0.0, 0.25, 1000);
563 assert!(
564 (v - 0.0).abs() < 1e-3,
565 "reverse repeat returns to start, got {v}"
566 );
567 assert_eq!(terminal_value(&driver, 0.0), 0.0);
568 }
569
570 #[test]
571 fn infinite_repeat_never_finishes() {
572 let driver = Driver::Repeat {
573 animation: Box::new(timing(10.0, 1.0)),
574 count: -1,
575 reverse: true,
576 };
577 let mut r = build_runner(&driver, 0.0);
578 for _ in 0..1000 {
579 let (_v, done) = r.step(0.1);
580 assert!(!done, "infinite repeat must never report finished");
581 }
582 }
583}