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}