Skip to main content

goud_engine/core/input_manager/
buffer.rs

1//! Input buffering methods for sequence and combo detection.
2
3use std::time::Instant;
4
5use super::manager::InputManager;
6use super::types::InputBinding;
7
8impl InputManager {
9    // === Input Buffering ===
10
11    /// Returns the current buffer duration.
12    pub fn buffer_duration(&self) -> std::time::Duration {
13        self.buffer_duration
14    }
15
16    /// Sets the buffer duration for input sequences.
17    ///
18    /// This determines how long inputs are remembered for combo detection.
19    pub fn set_buffer_duration(&mut self, duration: std::time::Duration) {
20        self.buffer_duration = duration;
21    }
22
23    /// Returns the number of inputs currently in the buffer.
24    pub fn buffer_size(&self) -> usize {
25        self.input_buffer.len()
26    }
27
28    /// Clears the input buffer.
29    ///
30    /// Useful when resetting combos or canceling sequences.
31    pub fn clear_buffer(&mut self) {
32        self.input_buffer.clear();
33    }
34
35    /// Checks if a sequence of inputs was pressed within the buffer duration.
36    ///
37    /// Returns true if all bindings in the sequence were pressed in order,
38    /// with each subsequent input occurring within the buffer window.
39    ///
40    /// # Example
41    ///
42    /// ```ignore
43    /// use goud_engine::ecs::{InputManager, InputBinding};
44    /// use glfw::Key;
45    ///
46    /// let mut input = InputManager::new();
47    ///
48    /// // Detect "Down, Down, Forward, Punch" combo (fighting game)
49    /// let combo = vec![
50    ///     InputBinding::Key(Key::Down),
51    ///     InputBinding::Key(Key::Down),
52    ///     InputBinding::Key(Key::Right),
53    ///     InputBinding::Key(Key::Space),
54    /// ];
55    ///
56    /// if input.sequence_detected(&combo) {
57    ///     player.perform_special_move();
58    /// }
59    /// ```
60    pub fn sequence_detected(&self, sequence: &[InputBinding]) -> bool {
61        if sequence.is_empty() || self.input_buffer.is_empty() {
62            return false;
63        }
64
65        let now = Instant::now();
66        let mut seq_index = 0;
67
68        // Scan buffer from oldest to newest
69        for buffered in &self.input_buffer {
70            // Skip expired inputs
71            if buffered.is_expired(now, self.buffer_duration) {
72                continue;
73            }
74
75            // Check if this matches the next input in sequence
76            if buffered.binding == sequence[seq_index] {
77                seq_index += 1;
78
79                // Entire sequence matched
80                if seq_index == sequence.len() {
81                    return true;
82                }
83            }
84        }
85
86        false
87    }
88
89    /// Checks if a sequence was pressed and clears the buffer if detected.
90    ///
91    /// This is useful for consuming combos so they don't trigger multiple times.
92    ///
93    /// Returns true if the sequence was detected and consumed.
94    pub fn consume_sequence(&mut self, sequence: &[InputBinding]) -> bool {
95        if self.sequence_detected(sequence) {
96            self.clear_buffer();
97            true
98        } else {
99            false
100        }
101    }
102
103    /// Returns the time since the last buffered input in seconds.
104    ///
105    /// Returns None if the buffer is empty.
106    pub fn time_since_last_input(&self) -> Option<f32> {
107        self.input_buffer
108            .back()
109            .map(|input| input.age(Instant::now()))
110    }
111
112    /// Returns all inputs in the buffer (oldest to newest).
113    ///
114    /// Useful for debugging or visualizing input history.
115    pub fn buffered_inputs(&self) -> impl Iterator<Item = (InputBinding, f32)> + '_ {
116        let now = Instant::now();
117        self.input_buffer
118            .iter()
119            .map(move |input| (input.binding, input.age(now)))
120    }
121}