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    #[ignore = "Behavior has subtly changed around Bevy 0.16, needs investigation"]
105    fn durations() {
106        use crate::action_state::ActionState;
107        use bevy::platform::time::Instant;
108        use core::time::Duration;
109
110        let mut action_state = ActionState::<Action>::default();
111
112        // Actions start released
113        assert!(action_state.released(&Action::Jump));
114        assert_eq!(action_state.instant_started(&Action::Jump), None,);
115        assert_eq!(action_state.current_duration(&Action::Jump), Duration::ZERO);
116        assert_eq!(
117            action_state.previous_duration(&Action::Jump),
118            Duration::ZERO
119        );
120
121        // Pressing a button swaps the state
122        action_state.press(&Action::Jump);
123        assert!(action_state.pressed(&Action::Jump));
124        assert_eq!(action_state.instant_started(&Action::Jump), None);
125        assert_eq!(action_state.current_duration(&Action::Jump), Duration::ZERO);
126        assert_eq!(
127            action_state.previous_duration(&Action::Jump),
128            Duration::ZERO
129        );
130
131        // Ticking time sets the instant for the new state
132        let t0 = Instant::now();
133        let t1 = t0 + Duration::new(1, 0);
134
135        action_state.tick(t1, t0);
136        assert_eq!(action_state.instant_started(&Action::Jump), Some(t0));
137        assert_eq!(action_state.current_duration(&Action::Jump), t1 - t0);
138        assert_eq!(
139            action_state.previous_duration(&Action::Jump),
140            Duration::ZERO
141        );
142
143        // Time passes
144        let t2 = t1 + Duration::new(5, 0);
145
146        // The duration is updated
147        action_state.tick(t2, t1);
148        assert_eq!(action_state.instant_started(&Action::Jump), Some(t0));
149        assert_eq!(action_state.current_duration(&Action::Jump), t2 - t0);
150        assert_eq!(
151            action_state.previous_duration(&Action::Jump),
152            Duration::ZERO
153        );
154
155        // Releasing again, swapping the current duration to the previous one
156        action_state.release(&Action::Jump);
157        assert_eq!(action_state.instant_started(&Action::Jump), None);
158        assert_eq!(action_state.current_duration(&Action::Jump), Duration::ZERO);
159        assert_eq!(action_state.previous_duration(&Action::Jump), t2 - t0);
160    }
161}