rsanim/lib.rs
1#![warn(missing_docs)]
2//! # Rust Sprite Animator
3//!
4//! A basic state machine for managing sprite animations.
5//!
6//! Example usage:
7//!
8//! ```
9//! use rsanim::prelude::*;
10//!
11//! #[derive(Clone, Eq, PartialEq, Hash, Debug)]
12//! enum Animation {
13//! Idle,
14//! Run,
15//! }
16//!
17//! #[derive(Clone, Debug, PartialEq)]
18//! struct Params {
19//! pub speed: f32,
20//! }
21//!
22//! let mut state_machine = StateMachine::new(
23//! Animation::Idle,
24//! HashMap::from([
25//! (
26//! Animation::Idle,
27//! State {
28//! duration: 0.5,
29//! repeat: true,
30//! },
31//! ),
32//! (
33//! Animation::Run,
34//! State {
35//! duration: 1.0,
36//! repeat: true,
37//! },
38//! ),
39//! ]),
40//! vec![
41//! Transition {
42//! start_state: TransitionStartState::Node(Animation::Idle),
43//! end_state: TransitionEndState::Node(Animation::Run),
44//! trigger: TransitionTrigger::Condition(Box::new(|x: &Params| x.speed > 0.0)),
45//! },
46//! Transition {
47//! start_state: TransitionStartState::Node(Animation::Run),
48//! end_state: TransitionEndState::Node(Animation::Idle),
49//! trigger: TransitionTrigger::Condition(Box::new(|x: &Params| x.speed <= 0.0)),
50//! },
51//! ],
52//! Params { speed: 0.0 },
53//! )
54//! .unwrap();
55//!
56//! let animator = Animator::new(
57//! state_machine,
58//! HashMap::from([
59//! (
60//! Animation::Idle,
61//! vec![
62//! Frame {
63//! value: 0,
64//! progress: 0.00,
65//! },
66//! Frame {
67//! value: 1,
68//! progress: 0.33,
69//! },
70//! Frame {
71//! value: 2,
72//! progress: 0.67,
73//! },
74//! ],
75//! ),
76//! (
77//! Animation::Run,
78//! vec![
79//! Frame {
80//! value: 0,
81//! progress: 0.00,
82//! },
83//! Frame {
84//! value: 1,
85//! progress: 0.33,
86//! },
87//! Frame {
88//! value: 2,
89//! progress: 0.67,
90//! },
91//! ],
92//! ),
93//! ]),
94//! )
95//! .unwrap();
96//! ```
97//!
98//! Update the state machine's elapsed time:
99//!
100//! ```
101//! # use rsanim::prelude::*;
102//!
103//! # #[derive(Clone, Eq, PartialEq, Hash, Debug)]
104//! # enum Animation {
105//! # Idle,
106//! # Run,
107//! # }
108//!
109//! # #[derive(Clone, Debug, PartialEq)]
110//! # struct Params {
111//! # pub speed: f32,
112//! # }
113//!
114//! # let mut state_machine = StateMachine::new(
115//! # Animation::Idle,
116//! # HashMap::from([
117//! # (
118//! # Animation::Idle,
119//! # State {
120//! # duration: 0.5,
121//! # repeat: true,
122//! # },
123//! # ),
124//! # (
125//! # Animation::Run,
126//! # State {
127//! # duration: 1.0,
128//! # repeat: true,
129//! # },
130//! # ),
131//! # ]),
132//! # vec![
133//! # Transition {
134//! # start_state: TransitionStartState::Node(Animation::Idle),
135//! # end_state: TransitionEndState::Node(Animation::Run),
136//! # trigger: TransitionTrigger::Condition(Box::new(|x: &Params| x.speed > 0.0)),
137//! # },
138//! # Transition {
139//! # start_state: TransitionStartState::Node(Animation::Run),
140//! # end_state: TransitionEndState::Node(Animation::Idle),
141//! # trigger: TransitionTrigger::Condition(Box::new(|x: &Params| x.speed <= 0.0)),
142//! # },
143//! # ],
144//! # Params { speed: 0.0 },
145//! # )
146//! # .unwrap();
147//!
148//! # let mut animator = Animator::new(
149//! # state_machine,
150//! # HashMap::from([
151//! # (
152//! # Animation::Idle,
153//! # vec![
154//! # Frame {
155//! # value: 0,
156//! # progress: 0.00,
157//! # },
158//! # Frame {
159//! # value: 1,
160//! # progress: 0.33,
161//! # },
162//! # Frame {
163//! # value: 2,
164//! # progress: 0.67,
165//! # },
166//! # ],
167//! # ),
168//! # (
169//! # Animation::Run,
170//! # vec![
171//! # Frame {
172//! # value: 0,
173//! # progress: 0.00,
174//! # },
175//! # Frame {
176//! # value: 1,
177//! # progress: 0.33,
178//! # },
179//! # Frame {
180//! # value: 2,
181//! # progress: 0.67,
182//! # },
183//! # ],
184//! # ),
185//! # ]),
186//! # )
187//! # .unwrap();
188//! # let delta_time = 0.1;
189//! animator.update(delta_time);
190//! ```
191//!
192//! Update the state machine's parameters that are used to determine conditional transitions:
193//!
194//! ```
195//! # use rsanim::prelude::*;
196//!
197//! # #[derive(Clone, Eq, PartialEq, Hash, Debug)]
198//! # enum Animation {
199//! # Idle,
200//! # Run,
201//! # }
202//!
203//! # #[derive(Clone, Debug, PartialEq)]
204//! # struct Params {
205//! # pub speed: f32,
206//! # }
207//!
208//! # let mut state_machine = StateMachine::new(
209//! # Animation::Idle,
210//! # HashMap::from([
211//! # (
212//! # Animation::Idle,
213//! # State {
214//! # duration: 0.5,
215//! # repeat: true,
216//! # },
217//! # ),
218//! # (
219//! # Animation::Run,
220//! # State {
221//! # duration: 1.0,
222//! # repeat: true,
223//! # },
224//! # ),
225//! # ]),
226//! # vec![
227//! # Transition {
228//! # start_state: TransitionStartState::Node(Animation::Idle),
229//! # end_state: TransitionEndState::Node(Animation::Run),
230//! # trigger: TransitionTrigger::Condition(Box::new(|x: &Params| x.speed > 0.0)),
231//! # },
232//! # Transition {
233//! # start_state: TransitionStartState::Node(Animation::Run),
234//! # end_state: TransitionEndState::Node(Animation::Idle),
235//! # trigger: TransitionTrigger::Condition(Box::new(|x: &Params| x.speed <= 0.0)),
236//! # },
237//! # ],
238//! # Params { speed: 0.0 },
239//! # )
240//! # .unwrap();
241//!
242//! # let mut animator = Animator::new(
243//! # state_machine,
244//! # HashMap::from([
245//! # (
246//! # Animation::Idle,
247//! # vec![
248//! # Frame {
249//! # value: 0,
250//! # progress: 0.00,
251//! # },
252//! # Frame {
253//! # value: 1,
254//! # progress: 0.33,
255//! # },
256//! # Frame {
257//! # value: 2,
258//! # progress: 0.67,
259//! # },
260//! # ],
261//! # ),
262//! # (
263//! # Animation::Run,
264//! # vec![
265//! # Frame {
266//! # value: 0,
267//! # progress: 0.00,
268//! # },
269//! # Frame {
270//! # value: 1,
271//! # progress: 0.33,
272//! # },
273//! # Frame {
274//! # value: 2,
275//! # progress: 0.67,
276//! # },
277//! # ],
278//! # ),
279//! # ]),
280//! # )
281//! # .unwrap();
282//! animator.update_parameters(&|x| {
283//! x.speed = 1.0;
284//! });
285//! ```
286
287use std::fmt::{Debug, Formatter};
288use std::hash::{BuildHasherDefault, Hash};
289
290use ahash::AHasher;
291
292/// A [`HashMap`][hashbrown::HashMap] implementing aHash, a high
293/// speed keyed hashing algorithm intended for use in in-memory hashmaps.
294///
295/// aHash is designed for performance and is NOT cryptographically secure.
296///
297/// Within the same execution of the program iteration order of different
298/// `HashMap`s only depends on the order of insertions and deletions,
299/// but it will not be stable between multiple executions of the program.
300pub type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasherDefault<AHasher>>;
301
302/// A [`HashSet`][hashbrown::HashSet] implementing aHash, a high
303/// speed keyed hashing algorithm intended for use in in-memory hashmaps.
304///
305/// aHash is designed for performance and is NOT cryptographically secure.
306///
307/// Within the same execution of the program iteration order of different
308/// `HashSet`s only depends on the order of insertions and deletions,
309/// but it will not be stable between multiple executions of the program.
310pub type HashSet<K> = hashbrown::HashSet<K, BuildHasherDefault<AHasher>>;
311
312#[doc(hidden)]
313pub mod prelude {
314 pub use super::{
315 Animator, AnimatorError, CurrentState, Frame, HashMap, HashSet, State, StateMachine,
316 StateMachineError, Transition, TransitionEndState, TransitionStartState, TransitionTrigger,
317 };
318}
319
320/// The animator.
321///
322/// Used for translating the state machine into a sequence of frames.
323///
324/// ```
325/// # use rsanim::prelude::*;
326///
327/// #[derive(Clone, Eq, PartialEq, Hash, Debug)]
328/// enum Animation {
329/// Idle,
330/// Run,
331/// }
332///
333/// #[derive(Clone, Debug, PartialEq)]
334/// struct Params {
335/// pub speed: f32,
336/// }
337///
338/// let mut state_machine = StateMachine::new(
339/// Animation::Idle,
340/// HashMap::from([
341/// (
342/// Animation::Idle,
343/// State {
344/// duration: 0.5,
345/// repeat: true,
346/// },
347/// ),
348/// (
349/// Animation::Run,
350/// State {
351/// duration: 1.0,
352/// repeat: true,
353/// },
354/// ),
355/// ]),
356/// vec![
357/// Transition {
358/// start_state: TransitionStartState::Node(Animation::Idle),
359/// end_state: TransitionEndState::Node(Animation::Run),
360/// trigger: TransitionTrigger::Condition(Box::new(|x: &Params| x.speed > 0.0)),
361/// },
362/// Transition {
363/// start_state: TransitionStartState::Node(Animation::Run),
364/// end_state: TransitionEndState::Node(Animation::Idle),
365/// trigger: TransitionTrigger::Condition(Box::new(|x: &Params| x.speed <= 0.0)),
366/// },
367/// ],
368/// Params { speed: 0.0 },
369/// )
370/// .unwrap();
371///
372/// let mut animator = Animator::new(
373/// state_machine,
374/// HashMap::from([
375/// (
376/// Animation::Idle,
377/// vec![
378/// Frame {
379/// value: 0,
380/// progress: 0.00,
381/// },
382/// Frame {
383/// value: 1,
384/// progress: 0.33,
385/// },
386/// Frame {
387/// value: 2,
388/// progress: 0.67,
389/// },
390/// ],
391/// ),
392/// (
393/// Animation::Run,
394/// vec![
395/// Frame {
396/// value: 0,
397/// progress: 0.00,
398/// },
399/// Frame {
400/// value: 1,
401/// progress: 0.33,
402/// },
403/// Frame {
404/// value: 2,
405/// progress: 0.67,
406/// },
407/// ],
408/// ),
409/// ]),
410/// )
411/// .unwrap();
412///
413/// animator.update_parameters(&|x| {
414/// x.speed = 1.0;
415/// });
416///
417/// animator.update(0.1);
418/// ```
419#[derive(Clone, Debug)]
420pub struct Animator<K, V, F> {
421 state_machine: StateMachine<K, V>,
422 state_frames: HashMap<K, Vec<Frame<F>>>,
423}
424
425impl<K, V, F> Animator<K, V, F>
426where
427 K: Clone + Eq + PartialEq + Hash,
428{
429 /// Creates a new [`Animator`]
430 pub fn new(
431 state_machine: StateMachine<K, V>,
432 state_frames: HashMap<K, Vec<Frame<F>>>,
433 ) -> Result<Self, AnimatorError<K>> {
434 for state in state_machine.states.keys() {
435 match state_frames.get(state) {
436 Some(frames) => {
437 if frames.is_empty() {
438 return Err(AnimatorError::EmptyStateFrames(state.clone()));
439 }
440
441 // make sure frames are sorted by progress
442 let mut last_progress = -1.0;
443 for frame in frames {
444 if frame.progress < last_progress {
445 return Err(AnimatorError::UnsortedStateFrames(state.clone()));
446 }
447 if frame.progress < 0.0 || frame.progress > 1.0 {
448 return Err(AnimatorError::InvalidStateFrameProgress(
449 state.clone(),
450 frame.progress,
451 ));
452 }
453 last_progress = frame.progress;
454 }
455 }
456 None => return Err(AnimatorError::MissingStateFrames(state.clone())),
457 }
458 }
459
460 Ok(Self {
461 state_machine,
462 state_frames,
463 })
464 }
465
466 /// Updates elapsed time
467 pub fn update(&mut self, delta_time: f32) {
468 self.state_machine.update(delta_time);
469 }
470
471 /// Updates the parameters
472 pub fn update_parameters(&mut self, update: &dyn Fn(&mut V)) {
473 self.state_machine.update_parameters(update);
474 }
475
476 /// Returns the current state
477 pub fn state(&self) -> &CurrentState<K> {
478 self.state_machine.state()
479 }
480
481 /// Updates the parameters
482 pub fn parameters(&self) -> &V {
483 self.state_machine.parameters()
484 }
485
486 /// Returns the current frame
487 pub fn frame(&self) -> &F {
488 let current_state = self.state_machine.state();
489 let frames = match self.state_frames.get(¤t_state.key) {
490 Some(frames) => frames,
491 None => unreachable!(),
492 };
493
494 let progress = current_state.progress();
495 let mut frame = &frames[0];
496 for f in frames {
497 if f.progress > progress {
498 return &frame.value;
499 }
500 frame = f;
501 }
502 &frame.value
503 }
504}
505
506/// An animation frame
507#[derive(Clone, Debug)]
508pub struct Frame<T> {
509 /// When the frame should be displayed [0.0, 1.0).
510 pub progress: f32,
511 /// The frame value.
512 pub value: T,
513}
514
515/// A animator error
516#[derive(Clone, PartialEq, Debug)]
517pub enum AnimatorError<K> {
518 /// The state machine contains a state with no frames.
519 EmptyStateFrames(K),
520 /// The state machine contains a state without any frames.
521 MissingStateFrames(K),
522 /// The state frames should be sorted by progress.
523 UnsortedStateFrames(K),
524 /// The state frame progress is invalid.
525 InvalidStateFrameProgress(K, f32),
526}
527
528/// The state machine.
529///
530/// Use this to track an entity's animation state.
531///
532/// ```
533/// # use rsanim::prelude::*;
534///
535/// #[derive(Clone, Eq, PartialEq, Hash, Debug)]
536/// enum Animation {
537/// Idle,
538/// Run,
539/// }
540///
541/// #[derive(Clone, Debug, PartialEq)]
542/// struct Params {
543/// pub speed: f32,
544/// }
545///
546/// let mut state_machine = StateMachine::new(
547/// Animation::Idle,
548/// HashMap::from([
549/// (
550/// Animation::Idle,
551/// State {
552/// duration: 0.5,
553/// repeat: true,
554/// },
555/// ),
556/// (
557/// Animation::Run,
558/// State {
559/// duration: 1.0,
560/// repeat: true,
561/// },
562/// ),
563/// ]),
564/// vec![
565/// Transition {
566/// start_state: TransitionStartState::Node(Animation::Idle),
567/// end_state: TransitionEndState::Node(Animation::Run),
568/// trigger: TransitionTrigger::Condition(Box::new(|x: &Params| x.speed > 0.0)),
569/// },
570/// Transition {
571/// start_state: TransitionStartState::Node(Animation::Run),
572/// end_state: TransitionEndState::Node(Animation::Idle),
573/// trigger: TransitionTrigger::Condition(Box::new(|x: &Params| x.speed <= 0.0)),
574/// },
575/// ],
576/// Params { speed: 0.0 },
577/// )
578/// .unwrap();
579///
580/// state_machine.update_parameters(&|x| {
581/// x.speed = 1.0;
582/// });
583///
584/// state_machine.update(0.1);
585/// ```
586#[derive(Clone, Debug)]
587pub struct StateMachine<K, V> {
588 pub(crate) current_state: CurrentState<K>,
589 pub(crate) states: HashMap<K, State>,
590 pub(crate) transitions: Vec<Transition<K, V>>,
591 pub(crate) parameters: V,
592}
593
594impl<K, V> StateMachine<K, V>
595where
596 K: Clone + Eq + PartialEq + Hash,
597{
598 /// Creates a new [`StateMachine`]
599 pub fn new(
600 starting_state: K,
601 states: HashMap<K, State>,
602 transitions: Vec<Transition<K, V>>,
603 parameters: V,
604 ) -> Result<Self, StateMachineError<K>> {
605 // validate that the starting state exists
606 let start = match states.get(&starting_state) {
607 Some(state) => state,
608 None => {
609 return Err(StateMachineError::InvalidStartingState(starting_state));
610 }
611 };
612 // validate that the start and end states of each transition exist
613 for transition in &transitions {
614 match &transition.start_state {
615 TransitionStartState::Any => {}
616 TransitionStartState::Node(key) => {
617 if !states.contains_key(key) {
618 return Err(StateMachineError::InvalidTransitionStartState(key.clone()));
619 }
620 }
621 }
622 match &transition.end_state {
623 TransitionEndState::Node(key) => {
624 if !states.contains_key(key) {
625 return Err(StateMachineError::InvalidTransitionEndState(key.clone()));
626 }
627 }
628 }
629 }
630 Ok(Self {
631 current_state: CurrentState {
632 key: starting_state,
633 duration: start.duration,
634 elapsed: 0.0,
635 repeat: start.repeat,
636 },
637 states,
638 transitions,
639 parameters,
640 })
641 }
642
643 /// Returns the current state
644 pub fn state(&self) -> &CurrentState<K> {
645 &self.current_state
646 }
647
648 /// Returns the parameters
649 pub fn parameters(&self) -> &V {
650 &self.parameters
651 }
652
653 fn transition(&mut self) {
654 let mut visited = HashSet::new();
655
656 loop {
657 let start_state = TransitionStartState::Node(self.current_state.key.clone());
658 let state_ended = self.current_state.elapsed >= self.current_state.duration;
659 if let Some(transition) = self.transitions.iter().find(|x| {
660 (x.start_state == start_state || x.start_state == TransitionStartState::Any)
661 && match &x.end_state {
662 TransitionEndState::Node(node) => node != &self.current_state.key,
663 }
664 && match &x.trigger {
665 TransitionTrigger::Condition(condition) => condition(&self.parameters),
666 TransitionTrigger::End => state_ended,
667 }
668 }) {
669 let TransitionEndState::Node(end_state_key) = &transition.end_state;
670
671 if visited.contains(end_state_key) {
672 // We have already visited this state, so we should stop
673 break;
674 }
675
676 let end_state = match self.states.get(end_state_key) {
677 Some(state) => state,
678 None => unreachable!(),
679 };
680
681 self.current_state.key = end_state_key.clone();
682 self.current_state.duration = end_state.duration;
683 self.current_state.elapsed = 0.0;
684 self.current_state.repeat = end_state.repeat;
685
686 visited.insert(end_state_key.clone());
687 } else {
688 break;
689 }
690 }
691 }
692
693 /// Updates the parameters
694 pub fn update_parameters(&mut self, update: &dyn Fn(&mut V)) {
695 update(&mut self.parameters);
696
697 let start_state = TransitionStartState::Node(self.current_state.key.clone());
698
699 // Only trigger conditional transitions since the time has not changed
700 if let Some(transition) = self.transitions.iter().find(|x| {
701 (x.start_state == start_state || x.start_state == TransitionStartState::Any)
702 && match &x.end_state {
703 TransitionEndState::Node(node) => node != &self.current_state.key,
704 }
705 && match &x.trigger {
706 TransitionTrigger::Condition(condition) => condition(&self.parameters),
707 _ => false,
708 }
709 }) {
710 let TransitionEndState::Node(end_state_key) = &transition.end_state;
711 let end_state = match self.states.get(end_state_key) {
712 Some(state) => state,
713 None => unreachable!(),
714 };
715
716 self.current_state.key = end_state_key.clone();
717 self.current_state.duration = end_state.duration;
718 self.current_state.elapsed = 0.0;
719 self.current_state.repeat = end_state.repeat;
720
721 // Make sure we transition through any more transitions
722 self.transition();
723 };
724 }
725
726 /// Updates elapsed time
727 pub fn update(&mut self, delta_time: f32) {
728 if self.current_state.elapsed < self.current_state.duration {
729 self.current_state.elapsed += delta_time;
730
731 if self.current_state.elapsed >= self.current_state.duration {
732 if self.current_state.repeat {
733 self.current_state.elapsed %= self.current_state.duration
734 } else {
735 self.current_state.elapsed = self.current_state.duration;
736 }
737
738 let start_state = TransitionStartState::Node(self.current_state.key.clone());
739
740 // Only trigger end transitions since the parameters have not changed
741 if let Some(transition) = self.transitions.iter().find(|x| {
742 matches!(x.trigger, TransitionTrigger::End)
743 && (x.start_state == start_state
744 || x.start_state == TransitionStartState::Any)
745 && match &x.end_state {
746 TransitionEndState::Node(node) => node != &self.current_state.key,
747 }
748 }) {
749 let TransitionEndState::Node(end_state_key) = &transition.end_state;
750 let end_state = match self.states.get(end_state_key) {
751 Some(state) => state,
752 None => unreachable!(),
753 };
754
755 self.current_state.key = end_state_key.clone();
756 self.current_state.duration = end_state.duration;
757 self.current_state.elapsed = 0.0;
758 self.current_state.repeat = end_state.repeat;
759
760 // Make sure we transition through any more transitions
761 self.transition();
762 }
763 }
764 }
765 }
766}
767
768/// A state machine error
769#[derive(Clone, PartialEq, Debug)]
770pub enum StateMachineError<K> {
771 /// The starting state does not exist
772 InvalidStartingState(K),
773 /// The start state of a transition does not exist
774 InvalidTransitionStartState(K),
775 /// The end state of a transition does not exist
776 InvalidTransitionEndState(K),
777}
778
779/// A state machine's current state
780#[derive(Clone, PartialEq, Debug)]
781pub struct CurrentState<K> {
782 /// The current state key
783 pub key: K,
784 /// The current state duration
785 pub duration: f32,
786 /// The current state elapsed time
787 pub elapsed: f32,
788 /// Whether the current state repeats
789 pub repeat: bool,
790}
791
792impl<K> CurrentState<K> {
793 /// Returns the current state's progress [0.0, 1.0)
794 pub fn progress(&self) -> f32 {
795 self.elapsed / self.duration
796 }
797
798 /// Returns whether the current state is finished
799 pub fn finished(&self) -> bool {
800 self.elapsed >= self.duration
801 }
802}
803
804/// A state
805#[derive(Clone, PartialEq, Debug)]
806pub struct State {
807 /// The state duration
808 pub duration: f32,
809 /// Whether the state repeats
810 pub repeat: bool,
811}
812
813/// A transition
814#[derive(Clone, Debug)]
815pub struct Transition<K, V> {
816 /// The start state
817 pub start_state: TransitionStartState<K>,
818 /// The end state
819 pub end_state: TransitionEndState<K>,
820 /// The trigger
821 pub trigger: TransitionTrigger<V>,
822}
823
824/// A transition start state
825#[derive(Clone, Eq, PartialEq, Debug)]
826pub enum TransitionStartState<K> {
827 /// Any state
828 Any,
829 /// A specific state
830 Node(K),
831}
832
833/// A transition end state
834#[derive(Clone, Eq, PartialEq, Debug)]
835pub enum TransitionEndState<K> {
836 /// A specific state
837 Node(K),
838}
839
840/// A trigger
841#[derive(Clone)]
842pub enum TransitionTrigger<V> {
843 /// A condition
844 Condition(Box<fn(&V) -> bool>),
845 /// End
846 End,
847}
848
849impl<V> Debug for TransitionTrigger<V> {
850 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
851 match self {
852 TransitionTrigger::Condition(_) => write!(f, "Condition"),
853 TransitionTrigger::End => write!(f, "End"),
854 }
855 }
856}