puppetmaster/
event.rs

1use std::hash::Hash;
2
3use ahash::{AHashMap, AHashSet};
4
5/// Input handler for an event-based game engine.
6///
7/// Use this when your game engine provides inputs via an event system.
8///
9/// At the top of your game loop, you MUST call [`EventInputHandler::update`] to
10/// process the input events its received. The general logic should look like this:
11///
12/// ```rust
13/// # use puppetmaster::EventInputHandler;
14/// // This is predefined by your game engine
15/// #[derive(Clone, Copy, Hash, Eq, PartialEq)]
16/// enum Key {
17///     Up, Down, Left, Right, Escape,
18///     // etc ...
19/// }
20///
21/// // This is also predefined by your game engine
22/// enum Event {
23///     KeyDown(Key),
24///     KeyUp(Key),
25///     // etc ...
26/// }
27///
28/// // You define this!
29/// #[derive(Clone, Copy, Hash, Eq, PartialEq)]
30/// enum Control {
31///     Up,
32///     Down,
33///     Left,
34///     Right,
35///     Pause,
36/// }
37///
38/// let mut input_handler = EventInputHandler::new_with_controls(vec![
39///     (Key::Up, Control::Up),
40///     (Key::Down, Control::Down),
41///     (Key::Left, Control::Left),
42///     (Key::Right, Control::Right),
43///     (Key::Escape, Control::Pause),
44/// ]);
45///
46/// # fn next_event() -> Option<Event> { None }
47/// # struct Player { x: f32 }
48/// # impl Player { fn jump(&mut self) {}}
49/// # let mut player = Player { x: 0.0 };
50///
51/// loop {
52///     while let Some(evt) = next_event() {
53///         match evt {
54///             Event::KeyDown(key) => input_handler.on_input_down(key),  
55///             Event::KeyUp(key) => input_handler.on_input_up(key),
56///             _ => {}
57///         }    
58///     }
59///     
60///     // VERY IMPORTANT: call this before doing your game logic!
61///     input_handler.update();
62///
63///     // Now do game logic ...
64///     if input_handler.down(Control::Left) {
65///         player.x += 1.0;
66///     } else if input_handler.down(Control::Right) {
67///         player.x -= 1.0;
68///     } else if input_handler.clicked(Control::Up) {
69///         player.jump();
70///     }
71///
72/// # // so the doctest doesn't infinite loop
73/// # break;
74/// }
75/// ```
76///
77/// `I` is the type of your inputs, and `C` is the type of your controls.
78#[derive(Clone, Debug)]
79pub struct EventInputHandler<I, C> {
80    /// Maps inputs to the controls they activate
81    control_config: AHashMap<I, C>,
82    /// How long each control has been pressed
83    control_time: AHashMap<C, u32>,
84    /// This is loaded into `input_time` at the `update` method.
85    pressed_controls: AHashSet<C>,
86}
87
88impl<I: Hash + Eq + Clone, C: Hash + Eq + Clone> EventInputHandler<I, C> {
89    /// Create a new `EventInputHandler` with no control mapping.
90    pub fn new() -> Self {
91        Self::default()
92    }
93
94    /// Create a new `EventInputHandler` with the given mapping of inputs to controls.
95    ///
96    /// If two entries in the iterator have the same input, the first one will be clobbered
97    /// and the second one will remain.
98    pub fn new_with_controls(map: impl IntoIterator<Item = (I, C)>) -> Self {
99        let control_config = map.into_iter().collect();
100        Self {
101            control_config,
102            control_time: AHashMap::new(),
103            pressed_controls: AHashSet::new(),
104        }
105    }
106
107    /// Call this function when your game engine gives you a `KeyDown` event.
108    pub fn on_input_down(&mut self, input: I) {
109        if let Some(ctrl) = self.control_config.get(&input) {
110            self.pressed_controls.insert(ctrl.clone());
111        }
112    }
113
114    /// Call this function when your game engine gives you a `KeyUp` event.
115    pub fn on_input_up(&mut self, input: I) {
116        if let Some(ctrl) = self.control_config.get(&input) {
117            self.pressed_controls.remove(ctrl);
118        }
119    }
120
121    /// Manually unpress all inputs. This is like calling [`on_input_up`](Self::on_input_up) for every possible `I`.
122    ///
123    /// Note you should *not* have to call this at the beginning of your loop. (In fact, if you do,
124    /// your inputs will never be pressed.)
125    pub fn clear_inputs(&mut self) {
126        self.pressed_controls.clear();
127        // The input times will be cleared in the `update` method.
128    }
129
130    /// Update the input handler. You MUST CALL THIS FIRST THING in your game loop.
131    /// Otherwise things won't get updated correctly.
132    pub fn update(&mut self) {
133        for control in self.control_config.values() {
134            let pressed = self.pressed_controls.contains(control);
135            if pressed {
136                *self.control_time.entry(control.clone()).or_default() += 1;
137            } else {
138                self.control_time.insert(control.clone(), 0);
139            }
140        }
141    }
142
143    /// Return the number of frames the given control has been pressed for
144    pub fn press_time(&self, ctrl: C) -> u32 {
145        self.control_time.get(&ctrl).copied().unwrap_or_default()
146    }
147
148    /// Return if this control is held down (ie, the corresponding input has been pressed for 1 or more frames).
149    pub fn down(&self, ctrl: C) -> bool {
150        self.press_time(ctrl) >= 1
151    }
152
153    /// Return if this control is up.
154    pub fn up(&self, ctrl: C) -> bool {
155        self.press_time(ctrl) == 0
156    }
157
158    /// Return if this control was *clicked* down this frame (ie, the corresponding input was *just* pressed this frame).
159    pub fn clicked(&self, ctrl: C) -> bool {
160        self.press_time(ctrl) == 1
161    }
162}
163
164impl<I, C> Default for EventInputHandler<I, C> {
165    fn default() -> Self {
166        Self {
167            control_config: AHashMap::new(),
168            control_time: AHashMap::new(),
169            pressed_controls: AHashSet::new(),
170        }
171    }
172}