1use crate::random::Rng;
22
23#[derive(Debug, Clone, PartialEq)]
25pub enum SequenceStep {
26 PlayFrames {
28 frames: Vec<usize>,
30 frame_duration: f32,
32 },
33 Pause(f32),
35 RandomPause {
38 min: f32,
40 max: f32,
42 },
43 Action(u32),
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum SequenceLoop {
51 Once,
53 Repeat,
55 PingPongRepeat,
57 PingPongOnce,
59}
60
61#[derive(Debug, Clone)]
69pub struct AnimationSequence {
70 steps: Vec<SequenceStep>,
72 cursors: Vec<usize>,
74 held_frame: Option<usize>,
76 loop_mode: SequenceLoop,
77 step: usize,
79 timer: f32,
81 step_duration: Option<f32>,
84 forward: bool,
86 actions: std::collections::VecDeque<u32>,
88 finished: bool,
89 just_completed: bool,
90}
91
92impl AnimationSequence {
93 #[must_use]
95 pub fn builder() -> SequenceBuilder {
96 SequenceBuilder::new()
97 }
98
99 pub fn tick(&mut self, dt: f32, rng: &mut Rng) -> bool {
109 self.just_completed = false;
110 self.actions.clear();
111 if self.finished {
112 return false;
113 }
114 self.timer += dt;
115
116 let mut idle_advances = 0;
117 loop {
118 let duration = self.resolved_duration(rng);
119 if self.timer < duration {
120 return false;
121 }
122 if duration > 0.0 {
123 idle_advances = 0;
124 self.timer -= duration;
125 } else {
126 idle_advances += 1;
127 if idle_advances > self.steps.len() {
128 self.timer = 0.0;
129 return false;
130 }
131 }
132 if self.advance() {
133 self.finished = true;
134 self.just_completed = true;
135 self.timer = 0.0;
136 return true;
137 }
138 }
139 }
140
141 #[must_use]
146 pub fn current_frame(&self) -> Option<usize> {
147 match self.steps.get(self.step) {
148 Some(SequenceStep::PlayFrames { frames, .. }) => {
149 frames.get(self.cursors[self.step]).copied()
150 }
151 _ => self.held_frame,
152 }
153 }
154
155 #[must_use]
158 pub fn just_completed(&self) -> bool {
159 self.just_completed
160 }
161
162 #[must_use]
164 pub fn is_finished(&self) -> bool {
165 self.finished
166 }
167
168 pub fn take_action(&mut self) -> Option<u32> {
173 self.actions.pop_front()
174 }
175
176 pub fn reset(&mut self) {
179 self.step = 0;
180 self.timer = 0.0;
181 self.step_duration = None;
182 self.forward = true;
183 self.finished = false;
184 self.just_completed = false;
185 self.actions.clear();
186 self.cursors.fill(0);
187 self.held_frame = None;
188 }
189
190 fn resolved_duration(&mut self, rng: &mut Rng) -> f32 {
193 if let Some(duration) = self.step_duration {
194 return duration;
195 }
196 let duration = match &self.steps[self.step] {
197 SequenceStep::PlayFrames { frame_duration, .. } => *frame_duration,
198 SequenceStep::Pause(duration) => *duration,
199 SequenceStep::RandomPause { min, max } => min + rng.f32() * (max - min),
200 SequenceStep::Action(_) => 0.0,
201 };
202 self.step_duration = Some(duration);
203 duration
204 }
205
206 fn advance(&mut self) -> bool {
209 let cursor = self.cursors[self.step];
210 let next_frame = match &self.steps[self.step] {
211 SequenceStep::PlayFrames { frames, .. } => {
212 self.held_frame = Some(frames[cursor]);
213 if self.forward {
214 (cursor + 1 < frames.len()).then_some(cursor + 1)
215 } else {
216 cursor.checked_sub(1)
217 }
218 }
219 _ => None,
220 };
221 if let Some(cursor) = next_frame {
222 self.cursors[self.step] = cursor;
223 self.step_duration = None;
224 return false;
225 }
226 if let SequenceStep::Action(id) = &self.steps[self.step] {
227 self.actions.push_back(*id);
228 }
229 self.next_step()
230 }
231
232 fn next_step(&mut self) -> bool {
235 let previous_step = self.step;
236 let previous_forward = self.forward;
237
238 match self.loop_mode {
239 SequenceLoop::Once => {
240 self.step += 1;
241 if self.step >= self.steps.len() {
242 return true;
243 }
244 }
245 SequenceLoop::Repeat => {
246 self.step += 1;
247 if self.step >= self.steps.len() {
248 self.step = 0;
249 self.cursors.fill(0);
250 }
251 }
252 SequenceLoop::PingPongRepeat | SequenceLoop::PingPongOnce => {
253 if self.forward {
254 self.step += 1;
255 if self.step >= self.steps.len() {
256 self.step = self.steps.len().saturating_sub(2);
259 self.forward = false;
260 }
261 } else if self.step > 0 {
262 self.step -= 1;
263 } else if self.loop_mode == SequenceLoop::PingPongOnce {
264 return true;
265 } else {
266 self.step = usize::from(self.steps.len() > 1);
267 self.forward = true;
268 self.cursors.fill(0);
269 }
270 }
271 }
272
273 if self.step != previous_step || self.forward != previous_forward {
274 self.enter_step(previous_forward);
275 }
276 self.step_duration = None;
277 false
278 }
279
280 fn enter_step(&mut self, previous_forward: bool) {
286 let single_step = self.steps.len() == 1;
287 let forward = self.forward;
288 let SequenceStep::PlayFrames { frames, .. } = &self.steps[self.step] else {
289 return;
290 };
291 let len = frames.len();
292 self.cursors[self.step] = if forward {
293 usize::from(!previous_forward && single_step && len > 1)
294 } else if previous_forward && single_step && len > 1 {
295 len - 2
296 } else {
297 len - 1
298 };
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
304pub enum SequenceError {
305 NoSteps,
307 EmptyFrames { step: usize },
309 BadDuration { step: usize },
311 BadRandomRange { step: usize },
314}
315
316impl std::fmt::Display for SequenceError {
317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318 match self {
319 Self::NoSteps => write!(f, "animation sequence has no steps"),
320 Self::EmptyFrames { step } => {
321 write!(f, "animation sequence step {step} plays no frames")
322 }
323 Self::BadDuration { step } => write!(
324 f,
325 "animation sequence step {step} needs a finite, non-negative duration"
326 ),
327 Self::BadRandomRange { step } => write!(
328 f,
329 "animation sequence step {step} needs a finite random pause range with min <= max"
330 ),
331 }
332 }
333}
334
335impl std::error::Error for SequenceError {}
336
337#[derive(Debug, Clone)]
340pub struct SequenceBuilder {
341 steps: Vec<SequenceStep>,
342 loop_mode: SequenceLoop,
343}
344
345impl SequenceBuilder {
346 fn new() -> Self {
347 Self {
348 steps: Vec::new(),
349 loop_mode: SequenceLoop::Once,
350 }
351 }
352
353 #[must_use]
355 pub fn loop_mode(mut self, loop_mode: SequenceLoop) -> Self {
356 self.loop_mode = loop_mode;
357 self
358 }
359
360 #[must_use]
363 pub fn play_frames(mut self, frames: impl Into<Vec<usize>>, frame_duration: f32) -> Self {
364 self.steps.push(SequenceStep::PlayFrames {
365 frames: frames.into(),
366 frame_duration,
367 });
368 self
369 }
370
371 #[must_use]
373 pub fn play_frame_range(self, first: usize, last: usize, frame_duration: f32) -> Self {
374 let frames: Vec<usize> = if first <= last {
375 (first..=last).collect()
376 } else {
377 Vec::new()
378 };
379 self.play_frames(frames, frame_duration)
380 }
381
382 #[must_use]
384 pub fn pause(mut self, duration: f32) -> Self {
385 self.steps.push(SequenceStep::Pause(duration));
386 self
387 }
388
389 #[must_use]
391 pub fn random_pause(mut self, min: f32, max: f32) -> Self {
392 self.steps.push(SequenceStep::RandomPause { min, max });
393 self
394 }
395
396 #[must_use]
398 pub fn action(mut self, id: u32) -> Self {
399 self.steps.push(SequenceStep::Action(id));
400 self
401 }
402
403 pub fn build(self) -> Result<AnimationSequence, SequenceError> {
406 if self.steps.is_empty() {
407 return Err(SequenceError::NoSteps);
408 }
409 for (step, item) in self.steps.iter().enumerate() {
410 match item {
411 SequenceStep::PlayFrames {
412 frames,
413 frame_duration,
414 } => {
415 if frames.is_empty() {
416 return Err(SequenceError::EmptyFrames { step });
417 }
418 if !frame_duration.is_finite() || *frame_duration < 0.0 {
419 return Err(SequenceError::BadDuration { step });
420 }
421 }
422 SequenceStep::Pause(duration) => {
423 if !duration.is_finite() || *duration < 0.0 {
424 return Err(SequenceError::BadDuration { step });
425 }
426 }
427 SequenceStep::RandomPause { min, max } => {
428 if !min.is_finite() || !max.is_finite() || *min < 0.0 || max < min {
429 return Err(SequenceError::BadRandomRange { step });
430 }
431 }
432 SequenceStep::Action(_) => {}
433 }
434 }
435 Ok(AnimationSequence {
436 cursors: vec![0; self.steps.len()],
437 held_frame: None,
438 steps: self.steps,
439 loop_mode: self.loop_mode,
440 step: 0,
441 timer: 0.0,
442 step_duration: None,
443 forward: true,
444 actions: std::collections::VecDeque::new(),
445 finished: false,
446 just_completed: false,
447 })
448 }
449}
450
451impl Default for SequenceBuilder {
452 fn default() -> Self {
453 Self::new()
454 }
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460 use wasm_bindgen_test::wasm_bindgen_test;
461
462 #[wasm_bindgen_test(unsupported = test)]
466 fn a_tick_over_zero_duration_steps_makes_one_full_pass() {
467 let mut sequence = AnimationSequence::builder()
468 .loop_mode(SequenceLoop::Repeat)
469 .action(1)
470 .action(2)
471 .build()
472 .unwrap();
473 let mut rng = rng();
474 for _ in 0..2 {
475 sequence.tick(0.5, &mut rng);
476 let mut fired = Vec::new();
477 while let Some(action) = sequence.take_action() {
478 fired.push(action);
479 }
480 assert_eq!(fired.len(), 2, "one action per step per tick: {fired:?}");
481 }
482 }
483
484 #[wasm_bindgen_test(unsupported = test)]
488 fn two_step_pingpong_repeat_restarts_one_step_in() {
489 let mut sequence = AnimationSequence::builder()
490 .loop_mode(SequenceLoop::PingPongRepeat)
491 .play_frames(vec![0, 1], 0.1)
492 .play_frames(vec![2, 3], 0.1)
493 .build()
494 .unwrap();
495 let mut rng = rng();
496 let mut seen = vec![sequence.current_frame()];
497 for _ in 0..9 {
498 sequence.tick(0.1, &mut rng);
499 seen.push(sequence.current_frame());
500 }
501 assert_eq!(seen, [0, 1, 2, 3, 1, 0, 2, 3, 1, 0].map(Some).to_vec());
502 }
503
504 #[wasm_bindgen_test(unsupported = test)]
507 fn a_remainder_carries_into_the_next_tick() {
508 let mut sequence = AnimationSequence::builder()
509 .loop_mode(SequenceLoop::Repeat)
510 .play_frames(vec![0, 1, 2, 3, 4], 0.1)
511 .build()
512 .unwrap();
513 let mut rng = rng();
514 sequence.tick(0.25, &mut rng);
515 assert_eq!(sequence.current_frame(), Some(2));
516 sequence.tick(0.05, &mut rng);
517 assert_eq!(
518 sequence.current_frame(),
519 Some(3),
520 "0.05 s remainder carried"
521 );
522 }
523
524 #[wasm_bindgen_test(unsupported = test)]
527 fn two_step_pingpong_enters_reversed_steps_at_their_last_frame() {
528 let mut sequence = AnimationSequence::builder()
529 .loop_mode(SequenceLoop::PingPongOnce)
530 .play_frames(vec![0, 1], 0.1)
531 .play_frames(vec![2, 3], 0.1)
532 .build()
533 .unwrap();
534 let mut rng = rng();
535 let mut seen = vec![sequence.current_frame()];
536 for _ in 0..6 {
537 sequence.tick(0.1, &mut rng);
538 seen.push(sequence.current_frame());
539 }
540 assert_eq!(
542 seen,
543 vec![
544 Some(0),
545 Some(1),
546 Some(2),
547 Some(3),
548 Some(1),
549 Some(0),
550 Some(0)
551 ]
552 );
553 assert!(sequence.is_finished());
554 }
555
556 #[wasm_bindgen_test(unsupported = test)]
559 fn a_single_one_frame_step_pingpongs_without_panicking() {
560 let mut sequence = AnimationSequence::builder()
561 .loop_mode(SequenceLoop::PingPongOnce)
562 .play_frames(vec![7], 0.1)
563 .build()
564 .unwrap();
565 let mut rng = rng();
566 for _ in 0..4 {
567 sequence.tick(0.1, &mut rng);
568 assert!(matches!(sequence.current_frame(), Some(7) | None));
569 }
570 assert!(sequence.is_finished());
571 }
572
573 #[wasm_bindgen_test(unsupported = test)]
576 fn the_builder_accepts_zero_durations_and_equal_random_bounds() {
577 assert!(
578 AnimationSequence::builder()
579 .play_frames(vec![0], 0.0)
580 .random_pause(0.5, 0.5)
581 .build()
582 .is_ok()
583 );
584 assert_eq!(
585 AnimationSequence::builder()
586 .random_pause(0.6, 0.5)
587 .build()
588 .err(),
589 Some(SequenceError::BadRandomRange { step: 0 })
590 );
591 }
592
593 fn rng() -> Rng {
594 Rng::from_seed(1)
595 }
596
597 fn play(seq: &mut AnimationSequence, dt: f32, ticks: usize) -> Vec<Option<usize>> {
600 let mut rng = rng();
601 let mut seen = vec![seq.current_frame()];
602 for _ in 0..ticks {
603 if seq.tick(dt, &mut rng) {
604 break;
605 }
606 seen.push(seq.current_frame());
607 }
608 seen
609 }
610
611 fn single(frames: [usize; 3], loop_mode: SequenceLoop) -> AnimationSequence {
612 AnimationSequence::builder()
613 .loop_mode(loop_mode)
614 .play_frames(frames, 0.1)
615 .build()
616 .expect("valid sequence")
617 }
618
619 #[wasm_bindgen_test(unsupported = test)]
622 fn single_step_pingpong_once_plays_up_and_back() {
623 let mut seq = single([0, 1, 2], SequenceLoop::PingPongOnce);
624 assert_eq!(
625 play(&mut seq, 0.1, 10),
626 vec![Some(0), Some(1), Some(2), Some(1), Some(0)]
627 );
628 assert!(seq.is_finished() && seq.just_completed());
629 let mut rng = rng();
630 assert!(!seq.tick(0.1, &mut rng), "the completion edge fires once");
631 assert!(!seq.just_completed());
632 }
633
634 #[wasm_bindgen_test(unsupported = test)]
636 fn single_step_pingpong_repeat_bounces_forever() {
637 let mut seq = single([0, 1, 2], SequenceLoop::PingPongRepeat);
638 assert_eq!(
639 play(&mut seq, 0.1, 8),
640 vec![
641 Some(0),
642 Some(1),
643 Some(2),
644 Some(1),
645 Some(0),
646 Some(1),
647 Some(2),
648 Some(1),
649 Some(0),
650 ]
651 );
652 assert!(!seq.is_finished());
653 }
654
655 #[wasm_bindgen_test(unsupported = test)]
658 fn multi_step_pingpong_reverses_step_order() {
659 let mut seq = AnimationSequence::builder()
660 .loop_mode(SequenceLoop::PingPongOnce)
661 .play_frames([0, 1], 0.1)
662 .play_frames([2, 3], 0.1)
663 .play_frames([4, 5], 0.1)
664 .build()
665 .expect("valid sequence");
666 assert_eq!(
670 play(&mut seq, 0.1, 20),
671 [0, 1, 2, 3, 4, 5, 3, 2, 1, 0].map(Some).to_vec()
672 );
673 assert!(seq.just_completed());
674 }
675
676 #[wasm_bindgen_test(unsupported = test)]
679 fn repeat_rewinds_every_step_on_wrap() {
680 let mut seq = AnimationSequence::builder()
681 .loop_mode(SequenceLoop::Repeat)
682 .play_frames([0, 1], 0.1)
683 .play_frames([7, 8], 0.1)
684 .build()
685 .expect("valid sequence");
686 assert_eq!(
687 play(&mut seq, 0.1, 8),
688 [0, 1, 7, 8, 0, 1, 7, 8, 0].map(Some).to_vec()
689 );
690 assert!(!seq.is_finished());
691 }
692
693 #[wasm_bindgen_test(unsupported = test)]
696 fn large_dt_steps_several_frames_and_carries_the_remainder() {
697 let mut seq = AnimationSequence::builder()
698 .loop_mode(SequenceLoop::Repeat)
699 .play_frames([0, 1, 2, 3], 0.1)
700 .build()
701 .expect("valid sequence");
702 let mut rng = rng();
703 assert!(!seq.tick(0.35, &mut rng));
705 assert_eq!(seq.current_frame(), Some(3));
706 assert!(!seq.tick(0.05, &mut rng));
708 assert_eq!(seq.current_frame(), Some(0));
709 }
710
711 #[wasm_bindgen_test(unsupported = test)]
713 fn leftover_time_carries_across_steps() {
714 let mut seq = AnimationSequence::builder()
715 .play_frames([0], 0.1)
716 .pause(0.1)
717 .play_frames([5, 6], 0.1)
718 .build()
719 .expect("valid sequence");
720 let mut rng = rng();
721 assert!(!seq.tick(0.25, &mut rng));
723 assert_eq!(seq.current_frame(), Some(5));
724 assert!(!seq.tick(0.05, &mut rng));
725 assert_eq!(seq.current_frame(), Some(6));
726 }
727
728 #[wasm_bindgen_test(unsupported = test)]
731 fn large_ticks_hold_the_last_frame_through_pauses() {
732 let frames = AnimationSequence::builder().play_frames([0, 1], 0.125);
733 for builder in [frames.clone().pause(1.0), frames.random_pause(1.0, 2.0)] {
734 for schedule in [&[0.25][..], &[0.125, 0.125][..]] {
735 let mut seq = builder.clone().build().unwrap();
736 let mut rng = rng();
737 for dt in schedule {
738 assert!(!seq.tick(*dt, &mut rng));
739 }
740 assert_eq!(seq.current_frame(), Some(1), "entered the pause");
741 assert!(!seq.tick(0.5, &mut rng));
742 assert_eq!(seq.current_frame(), Some(1), "still in the pause");
743 }
744 }
745 }
746
747 #[wasm_bindgen_test(unsupported = test)]
750 fn completing_tick_holds_the_last_traversed_frame() {
751 for (loop_mode, endpoint) in [(SequenceLoop::Once, 5), (SequenceLoop::PingPongOnce, 3)] {
752 let mut seq = AnimationSequence::builder()
753 .loop_mode(loop_mode)
754 .play_frames([3, 4, 5], 0.125)
755 .build()
756 .unwrap();
757 let mut rng = rng();
758 assert!(seq.tick(1.0, &mut rng));
759 assert_eq!(seq.current_frame(), Some(endpoint));
760 assert!(!seq.tick(1.0, &mut rng));
761 assert_eq!(seq.current_frame(), Some(endpoint));
762 }
763 }
764
765 #[wasm_bindgen_test(unsupported = test)]
768 fn reversed_pause_holds_the_frame_at_the_turnaround() {
769 let mut seq = AnimationSequence::builder()
770 .loop_mode(SequenceLoop::PingPongOnce)
771 .play_frames([0, 1], 0.125)
772 .pause(0.5)
773 .play_frames([2, 3], 0.125)
774 .build()
775 .unwrap();
776 let mut rng = rng();
777 assert!(!seq.tick(1.0, &mut rng));
778 assert_eq!(seq.current_frame(), Some(3), "reverse pause at 1.0 s");
779 assert!(seq.tick(0.75, &mut rng));
780 assert_eq!(seq.current_frame(), Some(0), "reverse run ends at 1.75 s");
781 }
782
783 #[wasm_bindgen_test(unsupported = test)]
786 fn reset_clears_the_held_frame_before_a_leading_pause() {
787 let mut seq = AnimationSequence::builder()
788 .pause(0.25)
789 .play_frames([4, 7], 0.125)
790 .build()
791 .unwrap();
792 let mut rng = rng();
793 assert!(seq.tick(0.5, &mut rng));
794 assert_eq!(seq.current_frame(), Some(7));
795 seq.reset();
796 assert!(!seq.tick(0.125, &mut rng));
797 assert_eq!(seq.current_frame(), None, "no frame reached after reset");
798 assert!(!seq.tick(0.125, &mut rng));
799 assert_eq!(seq.current_frame(), Some(4), "first frame reached again");
800 }
801
802 #[wasm_bindgen_test(unsupported = test)]
805 fn zero_duration_steps_cannot_spin_within_a_tick() {
806 let mut seq = AnimationSequence::builder()
807 .loop_mode(SequenceLoop::Repeat)
808 .action(3)
809 .pause(0.0)
810 .random_pause(0.0, 0.0)
811 .build()
812 .expect("valid sequence");
813 let mut rng = rng();
814 assert!(!seq.tick(60.0, &mut rng), "Repeat never completes");
815 assert_eq!(seq.take_action(), Some(3));
816 assert_eq!(seq.take_action(), None, "one pass fires one action");
817 assert!(!seq.tick(60.0, &mut rng));
818 }
819
820 #[wasm_bindgen_test(unsupported = test)]
823 fn actions_queue_in_order_and_drain_once() {
824 let mut seq = AnimationSequence::builder()
825 .play_frames([0], 0.1)
826 .action(7)
827 .action(9)
828 .play_frames([1], 0.1)
829 .build()
830 .expect("valid sequence");
831 let mut rng = rng();
832 assert_eq!(
833 seq.take_action(),
834 None,
835 "nothing fires before the first tick"
836 );
837 assert!(!seq.tick(0.1, &mut rng));
838 assert_eq!(seq.current_frame(), Some(1), "both actions were crossed");
839 assert_eq!(seq.take_action(), Some(7));
840 assert_eq!(seq.take_action(), Some(9));
841 assert_eq!(seq.take_action(), None);
842 assert!(seq.tick(0.1, &mut rng), "the last frame completes the run");
843 assert_eq!(seq.take_action(), None, "actions do not re-fire");
844 }
845
846 #[wasm_bindgen_test(unsupported = test)]
849 fn an_exact_random_pause_lasts_exactly_min() {
850 let mut seq = AnimationSequence::builder()
851 .play_frames([4], 0.125)
852 .random_pause(0.5, 0.5)
853 .build()
854 .expect("valid sequence");
855 let mut rng = rng();
856 assert!(!seq.tick(0.125, &mut rng), "off the frame, onto the pause");
857 assert_eq!(
858 seq.current_frame(),
859 Some(4),
860 "a pause holds its prior frame"
861 );
862 for tick in 1..4 {
863 assert!(
864 !seq.tick(0.125, &mut rng),
865 "still pausing after {tick} ticks"
866 );
867 }
868 assert!(seq.tick(0.125, &mut rng), "0.5 s of pause has elapsed");
869 }
870
871 #[wasm_bindgen_test(unsupported = test)]
875 fn random_pauses_are_bounded_and_seed_reproducible() {
876 let build = || {
877 AnimationSequence::builder()
878 .random_pause(0.4, 0.8)
879 .build()
880 .expect("valid sequence")
881 };
882 let ticks_to_finish = |seed: u64| {
884 let mut seq = build();
885 let mut rng = Rng::from_seed(seed);
886 (1..=100)
887 .find(|_| seq.tick(0.05, &mut rng))
888 .expect("the pause ends")
889 };
890 for seed in 0..32 {
891 let ticks = ticks_to_finish(seed);
892 assert!(
893 (8..=16).contains(&ticks),
894 "seed {seed} paused {ticks} ticks"
895 );
896 assert_eq!(ticks, ticks_to_finish(seed), "seed {seed} is reproducible");
897 }
898 }
899
900 #[wasm_bindgen_test(unsupported = test)]
903 fn steps_without_randomness_do_not_draw_from_the_rng() {
904 let mut seq = AnimationSequence::builder()
905 .loop_mode(SequenceLoop::Repeat)
906 .play_frames([0, 1], 0.1)
907 .pause(0.2)
908 .action(1)
909 .build()
910 .expect("valid sequence");
911 let mut used = Rng::from_seed(5);
912 for _ in 0..50 {
913 seq.tick(0.1, &mut used);
914 }
915 let mut untouched = Rng::from_seed(5);
916 assert_eq!(used.u32(0..u32::MAX), untouched.u32(0..u32::MAX));
917 }
918
919 #[wasm_bindgen_test(unsupported = test)]
921 fn reset_restarts_a_finished_sequence() {
922 let mut seq = single([3, 4, 5], SequenceLoop::Once);
923 let mut rng = rng();
924 while !seq.is_finished() {
925 seq.tick(0.1, &mut rng);
926 }
927 assert_eq!(seq.current_frame(), Some(5), "Once holds its last frame");
928 seq.reset();
929 assert!(!seq.is_finished() && !seq.just_completed());
930 assert_eq!(seq.current_frame(), Some(3));
931 assert!(!seq.tick(0.1, &mut rng));
932 assert_eq!(seq.current_frame(), Some(4));
933 }
934
935 #[wasm_bindgen_test(unsupported = test)]
938 fn the_builder_rejects_authoring_errors() {
939 let err = |builder: SequenceBuilder| builder.build().expect_err("authoring error");
940 assert_eq!(err(AnimationSequence::builder()), SequenceError::NoSteps);
941 assert_eq!(
942 err(AnimationSequence::builder().play_frames(Vec::new(), 0.1)),
943 SequenceError::EmptyFrames { step: 0 }
944 );
945 assert_eq!(
946 err(AnimationSequence::builder().play_frame_range(4, 2, 0.1)),
947 SequenceError::EmptyFrames { step: 0 },
948 "a backwards range plays nothing"
949 );
950 assert_eq!(
951 err(AnimationSequence::builder().play_frames([0], -0.1)),
952 SequenceError::BadDuration { step: 0 }
953 );
954 assert_eq!(
955 err(AnimationSequence::builder()
956 .play_frames([0], 0.1)
957 .pause(f32::NAN)),
958 SequenceError::BadDuration { step: 1 }
959 );
960 assert_eq!(
961 err(AnimationSequence::builder().pause(f32::INFINITY)),
962 SequenceError::BadDuration { step: 0 }
963 );
964 assert_eq!(
965 err(AnimationSequence::builder().random_pause(2.0, 1.0)),
966 SequenceError::BadRandomRange { step: 0 }
967 );
968 assert_eq!(
969 err(AnimationSequence::builder().random_pause(f32::NAN, 1.0)),
970 SequenceError::BadRandomRange { step: 0 }
971 );
972 assert!(
973 AnimationSequence::builder()
974 .random_pause(0.0, 0.0)
975 .build()
976 .is_ok(),
977 "a zero-length random pause is legitimate"
978 );
979 }
980
981 #[wasm_bindgen_test(unsupported = test)]
983 fn play_frame_range_is_inclusive() {
984 let mut seq = AnimationSequence::builder()
985 .loop_mode(SequenceLoop::Once)
986 .play_frame_range(8, 11, 0.1)
987 .build()
988 .expect("valid sequence");
989 assert_eq!(play(&mut seq, 0.1, 10), [8, 9, 10, 11].map(Some).to_vec());
990 }
991}