babalcore/
input_jump.rs

1use super::time_helper::*;
2use std::time::{Duration, Instant};
3
4/// By default, the stickiness is set to a bit more than 1/10th of a second.
5/// This means that if player pushes a jump control less than
6/// 1/10th of a second before it can actually jump, the jump
7/// is still recorded as valid.
8pub const DEFAULT_STICKINESS_SECS_F64: f64 = 0.16;
9
10/// Used to handle jump input. This is to encapsulate all
11/// the nitty-picky logic about considering a button is still
12/// pressed a fraction of seconds after it has been pressed, etc.
13/// It does not actually poll and/or listens to the real hardware,
14/// it just keeps track of what's happening with push/pop operations.
15#[derive(Debug)]
16pub struct InputJump {
17    /// How long a button should be considered pressed after
18    /// it has actually been pressed. This is used to work
19    /// around the fact that you might require a jump
20    /// just before you actually land.
21    pub stickiness: Option<Duration>,
22
23    first_pop: bool,
24    last_request: Option<Instant>,
25}
26
27impl InputJump {
28    /// Create a new input jump manager.
29    ///
30    /// # Examples
31    ///
32    /// ```
33    /// use babalcore::*;
34    ///
35    /// let _input_jump = InputJump::new();
36    /// ```
37    pub fn new() -> InputJump {
38        let mut ret = InputJump {
39            stickiness: None,
40            first_pop: false,
41            last_request: None,
42        };
43        ret.set_stickiness_secs_f64(Some(DEFAULT_STICKINESS_SECS_F64));
44        ret
45    }
46
47    /// Set the stickiness value, in seconds.
48    ///
49    /// # Examples
50    ///
51    /// ```
52    /// use babalcore::*;
53    ///
54    /// let mut input_jump = InputJump::new();
55    /// input_jump.set_stickiness_secs_f64(Some(0.1));
56    /// ```
57    pub fn set_stickiness_secs_f64(&mut self, stickiness_secs_f64: Option<f64>) {
58        self.stickiness = match stickiness_secs_f64 {
59            Some(m) => {
60                if m > 0.0 {
61                    Some(Duration::from_secs_f64(m))
62                } else {
63                    None
64                }
65            }
66            None => None,
67        };
68    }
69
70    /// Push a jump request. It is possible to omit the current
71    /// now timestamp, or you may provide your own. If omitted,
72    /// the default is the current instant (AKA now). Pushing
73    /// several jump requests will not queue them, only the
74    /// last one is registered.
75    ///
76    /// # Examples
77    ///
78    /// ```
79    /// use babalcore::*;
80    ///
81    /// let mut input_jump = InputJump::new();
82    /// // Player pressed the jump key, clicked on mouse
83    /// input_jump.push_jump(None);
84    /// ```
85    pub fn push_jump(&mut self, now: Option<Instant>) {
86        let now = unwrap_now(now);
87        self.last_request = Some(match self.last_request {
88            Some(last_request) => {
89                if last_request < now {
90                    now
91                } else {
92                    last_request
93                }
94            }
95            None => now,
96        });
97        self.last_request = Some(now);
98        self.first_pop = true;
99    }
100
101    /// Pop a jump request. It is possible to omit the current
102    /// now timestamp, or you may provide your own. If omitted,
103    /// the default is the current instant (AKA now). Once a
104    /// jump request is popped, it is cleared from the object.
105    /// However, if stickiness is set, it will continue to
106    /// return true for some time, defined by stickiness.
107    /// This is to avoid the following "bug": a player would
108    /// press jump just before it can actually jump (eg: the
109    /// ball did not land yet). But a fraction of a second later
110    /// it would be OK to jump... You'd want it to jump now.
111    /// To work around this, pop will return true for some time,
112    /// typically 1/10th of a second.
113    ///
114    /// # Examples
115    ///
116    /// ```
117    /// use babalcore::*;
118    ///
119    /// let mut input_jump = InputJump::new();
120    /// assert!(!input_jump.pop_jump(None));
121    /// input_jump.push_jump(None);
122    /// assert!(input_jump.pop_jump(None));
123    /// ```
124    pub fn pop_jump(&mut self, now: Option<Instant>) -> bool {
125        match self.last_request {
126            Some(last_request) => {
127                let state = match self.stickiness {
128                    Some(stickiness) => {
129                        let now = unwrap_now(now);
130                        if now >= last_request {
131                            let delay: Duration = now - last_request;
132                            delay <= stickiness
133                        } else {
134                            false
135                        }
136                    }
137                    None => false,
138                };
139                if !state {
140                    self.last_request = None
141                }
142                if self.first_pop {
143                    self.first_pop = false;
144                    true
145                } else {
146                    state
147                }
148            }
149            None => false,
150        }
151    }
152}
153
154impl std::default::Default for InputJump {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use crate::*;
163    use std::time::{Duration, Instant};
164
165    #[test]
166    fn test_default() {
167        let input_jump = InputJump::default();
168        assert_eq!(
169            Some(Duration::from_secs_f64(DEFAULT_STICKINESS_SECS_F64)),
170            input_jump.stickiness
171        );
172    }
173
174    #[test]
175    fn test_set_stickiness_secs_f64() {
176        let mut input_jump = InputJump::new();
177        assert_eq!(
178            Some(Duration::from_secs_f64(DEFAULT_STICKINESS_SECS_F64)),
179            input_jump.stickiness
180        );
181        input_jump.set_stickiness_secs_f64(None);
182        assert_eq!(None, input_jump.stickiness);
183        input_jump.set_stickiness_secs_f64(Some(0.5));
184        assert_eq!(Some(Duration::from_secs_f64(0.5)), input_jump.stickiness);
185    }
186
187    #[test]
188    fn test_push_pop_without_stickiness() {
189        let mut input_jump = InputJump::new();
190        let now = Instant::now();
191        input_jump.set_stickiness_secs_f64(None);
192        assert!(!input_jump.pop_jump(Some(now)));
193        input_jump.push_jump(Some(now));
194        assert!(input_jump.pop_jump(Some(now)));
195        assert!(!input_jump.pop_jump(Some(now)));
196        input_jump.push_jump(Some(now));
197        input_jump.push_jump(Some(now));
198        input_jump.push_jump(Some(now));
199        assert!(input_jump.pop_jump(Some(now)));
200        assert!(!input_jump.pop_jump(Some(now)));
201    }
202
203    #[test]
204    fn test_push_pop_with_stickiness() {
205        let mut input_jump = InputJump::new();
206        let now = Instant::now();
207        let short_enough = Duration::from_secs_f64(DEFAULT_STICKINESS_SECS_F64 - 0.001);
208        let too_long = Duration::from_secs_f64(DEFAULT_STICKINESS_SECS_F64 + 0.001);
209        assert!(!input_jump.pop_jump(Some(now)));
210        input_jump.push_jump(Some(now));
211        assert!(input_jump.pop_jump(Some(now)));
212        assert!(input_jump.pop_jump(Some(now)));
213        assert!(input_jump.pop_jump(Some(now + short_enough)));
214        assert!(input_jump.pop_jump(Some(now + short_enough)));
215        assert!(!input_jump.pop_jump(Some(now + too_long)));
216        assert!(!input_jump.pop_jump(Some(now + short_enough)));
217        assert!(!input_jump.pop_jump(Some(now)));
218    }
219
220    #[test]
221    fn test_push_pop_rearm() {
222        let mut input_jump = InputJump::new();
223        let now = Instant::now();
224        let extra_delay = Duration::from_secs_f64(DEFAULT_STICKINESS_SECS_F64 * 3.0);
225        assert!(!input_jump.pop_jump(Some(now)));
226        input_jump.push_jump(Some(now));
227        input_jump.push_jump(Some(now + extra_delay));
228        input_jump.push_jump(Some(now));
229        assert!(input_jump.pop_jump(Some(now + extra_delay)));
230    }
231}