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(&current_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}