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}