leafwing_input_manager/
timing.rs

1//! Information about when an action was pressed or released.
2
3use bevy::{platform::time::Instant, reflect::Reflect};
4use core::time::Duration;
5use serde::{Deserialize, Serialize};
6
7/// Stores information about when an action was pressed or released
8///
9/// This struct is principally used as a field on [`ButtonData`](crate::action_state::ButtonData),
10/// which itself lives inside an [`ActionState`](crate::action_state::ActionState).
11#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, Reflect)]
12pub struct Timing {
13    /// The [`Instant`] at which the button was pressed or released
14    /// Recorded as the [`Time`](bevy::time::Time) at the start of the tick after the state last changed.
15    /// If this is none, [`Timing::tick`] has not been called yet.
16    #[serde(skip)]
17    pub instant_started: Option<Instant>,
18    /// The [`Duration`] for which the button has been pressed or released.
19    ///
20    /// This begins at [`Duration::ZERO`] when [`ActionState::update`](crate::action_state::ActionState::update) is called.
21    pub current_duration: Duration,
22    /// The [`Duration`] for which the button was pressed or released before the state last changed.
23    pub previous_duration: Duration,
24}
25
26impl Timing {
27    /// The default timing for a button that has not been pressed or released
28    pub const NEW: Timing = Timing {
29        instant_started: None,
30        current_duration: Duration::ZERO,
31        previous_duration: Duration::ZERO,
32    };
33}
34
35impl PartialOrd for Timing {
36    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
37        self.current_duration.partial_cmp(&other.current_duration)
38    }
39}
40
41impl Timing {
42    /// Advances the `current_duration` of this timer
43    ///
44    /// If the `instant_started` is None, it will be set to the current time.
45    /// This design allows us to ensure that the timing is always synchronized with the start of each frame.
46    pub fn tick(&mut self, current_instant: Instant, previous_instant: Instant) {
47        if let Some(instant_started) = self.instant_started {
48            self.current_duration = current_instant - instant_started;
49        } else {
50            self.current_duration = current_instant - previous_instant;
51            self.instant_started = Some(previous_instant);
52        }
53    }
54
55    /// Flips the metaphorical hourglass, storing `current_duration` in `previous_duration` and resetting `instant_started`
56    ///
57    /// This method is called whenever actions are pressed or released
58    pub fn flip(&mut self) {
59        self.previous_duration = self.current_duration;
60        self.current_duration = Duration::ZERO;
61        self.instant_started = None;
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use crate as leafwing_input_manager;
68    use bevy::prelude::Reflect;
69    use leafwing_input_manager_macros::Actionlike;
70
71    #[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, Reflect)]
72    enum Action {
73        Run,
74        Jump,
75        Hide,
76    }
77
78    #[test]
79    fn time_tick_ticks_away() {
80        use crate::action_state::ActionState;
81        use bevy::platform::time::Instant;
82        use core::time::Duration;
83
84        let mut action_state = ActionState::<Action>::default();
85
86        // Actions start released (but not just released)
87        assert!(action_state.released(&Action::Run));
88        assert!(!action_state.just_released(&Action::Jump));
89
90        // Ticking causes buttons just released to no longer be just released
91        action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1));
92        assert!(action_state.released(&Action::Jump));
93        assert!(!action_state.just_released(&Action::Jump));
94        action_state.press(&Action::Jump);
95        assert!(action_state.just_pressed(&Action::Jump));
96
97        // Ticking causes buttons just pressed to no longer be just pressed
98        action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1));
99        assert!(action_state.pressed(&Action::Jump));
100        assert!(!action_state.just_pressed(&Action::Jump));
101    }
102
103    #[test]
104    fn durations() {
105        use crate::action_state::ActionState;
106        use bevy::platform::time::Instant;
107        use core::time::Duration;
108
109        let mut action_state = ActionState::<Action>::default();
110
111        // Actions start released
112        assert!(action_state.released(&Action::Jump));
113        assert_eq!(action_state.instant_started(&Action::Jump), None,);
114        assert_eq!(action_state.current_duration(&Action::Jump), Duration::ZERO);
115        assert_eq!(
116            action_state.previous_duration(&Action::Jump),
117            Duration::ZERO
118        );
119
120        // Pressing a button swaps the state
121        action_state.press(&Action::Jump);
122        assert!(action_state.pressed(&Action::Jump));
123        assert_eq!(action_state.instant_started(&Action::Jump), None);
124        assert_eq!(action_state.current_duration(&Action::Jump), Duration::ZERO);
125        assert_eq!(
126            action_state.previous_duration(&Action::Jump),
127            Duration::ZERO
128        );
129
130        // Ticking time sets the instant for the new state
131        let t0 = Instant::now();
132        let t1 = t0 + Duration::new(1, 0);
133
134        action_state.tick(t1, t0);
135        assert_eq!(action_state.instant_started(&Action::Jump), Some(t0));
136        assert_eq!(action_state.current_duration(&Action::Jump), t1 - t0);
137        assert_eq!(
138            action_state.previous_duration(&Action::Jump),
139            Duration::ZERO
140        );
141
142        // Time passes
143        let t2 = t1 + Duration::new(5, 0);
144
145        // The duration is updated
146        action_state.tick(t2, t1);
147        assert_eq!(action_state.instant_started(&Action::Jump), Some(t0));
148        assert_eq!(action_state.current_duration(&Action::Jump), t2 - t0);
149        assert_eq!(
150            action_state.previous_duration(&Action::Jump),
151            Duration::ZERO
152        );
153
154        // Releasing again, swapping the current duration to the previous one
155        action_state.release(&Action::Jump);
156        assert_eq!(action_state.instant_started(&Action::Jump), None);
157        assert_eq!(action_state.current_duration(&Action::Jump), Duration::ZERO);
158        assert_eq!(action_state.previous_duration(&Action::Jump), t2 - t0);
159    }
160}