1use std::collections::BTreeMap;
4const MAX_ACTIONS: usize = 256;
5const MAX_BINDINGS: usize = 64;
6const MAX_ACTIVE_CODES: usize = 1024;
7#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
8pub enum InputCode {
9 Key(u16),
10 Mouse(u8),
11 PadButton(u8),
12 PadAxis(u8),
13 Touch(u8),
14}
15#[derive(Clone, Copy, Debug)]
16pub struct Binding {
17 pub code: InputCode,
18 pub scale: f32,
19 pub deadzone: f32,
20}
21#[derive(Clone, Copy, Default, Debug, PartialEq)]
22pub struct Action {
23 pub value: f32,
24 pub pressed: bool,
25 pub released: bool,
26}
27struct Entry {
28 bindings: Box<[Binding]>,
29 state: Action,
30}
31#[derive(Default)]
32pub struct ActionMap {
33 entries: Vec<Entry>,
34 values: BTreeMap<InputCode, f32>,
35}
36impl ActionMap {
37 pub fn action(&mut self, bindings: Vec<Binding>) -> usize {
39 self.try_action(bindings)
40 .expect("action or binding capacity exceeded")
41 }
42 pub fn try_action(&mut self, bindings: Vec<Binding>) -> Option<usize> {
43 if self.entries.len() >= MAX_ACTIONS || bindings.len() > MAX_BINDINGS {
44 return None;
45 }
46 let id = self.entries.len();
47 self.entries.push(Entry {
48 bindings: bindings.into_boxed_slice(),
49 state: Action::default(),
50 });
51 self.recompute();
52 Some(id)
53 }
54 pub fn rebind(&mut self, id: usize, bindings: Vec<Binding>) -> bool {
56 if bindings.len() > MAX_BINDINGS {
57 return false;
58 }
59 let Some(entry) = self.entries.get_mut(id) else {
60 return false;
61 };
62 entry.bindings = bindings.into_boxed_slice();
63 self.recompute();
64 true
65 }
66 pub fn feed(&mut self, code: InputCode, value: f32) {
67 let value = if value.is_finite() {
68 value.clamp(-1.0, 1.0)
69 } else {
70 0.0
71 };
72 if value == 0.0 {
73 self.values.remove(&code);
74 } else {
75 if self.values.len() >= MAX_ACTIVE_CODES && !self.values.contains_key(&code) {
76 return;
77 }
78 self.values.insert(code, value);
79 }
80 self.recompute();
81 }
82 fn recompute(&mut self) {
83 for entry in &mut self.entries {
84 let value = entry
86 .bindings
87 .iter()
88 .map(|binding| {
89 let raw = f64::from(self.values.get(&binding.code).copied().unwrap_or(0.0));
90 let dead = if binding.deadzone.is_finite() {
91 f64::from(binding.deadzone.clamp(0.0, 0.99))
92 } else {
93 0.0
94 };
95 if raw.abs() <= dead || !binding.scale.is_finite() {
96 0.0
97 } else {
98 raw.signum() * (raw.abs() - dead) / (1.0 - dead) * f64::from(binding.scale)
99 }
100 })
101 .sum::<f64>()
102 .clamp(-1.0, 1.0) as f32;
103 let down = value.abs() > 0.001;
104 let was = entry.state.value.abs() > 0.001;
105 entry.state.pressed |= down && !was;
106 entry.state.released |= !down && was;
107 entry.state.value = value;
108 }
109 }
110 pub fn peek(&self, id: usize) -> Action {
112 self.entries
113 .get(id)
114 .map_or(Action::default(), |entry| entry.state)
115 }
116 pub fn consume_tick(&mut self, id: usize) -> Action {
117 let Some(entry) = self.entries.get_mut(id) else {
118 return Action::default();
119 };
120 let state = entry.state;
121 entry.state.pressed = false;
122 entry.state.released = false;
123 state
124 }
125 pub fn lose_focus(&mut self) {
126 self.values.clear();
127 self.recompute();
128 }
129}
130#[cfg(test)]
131mod tests {
132 use super::*;
133 fn binding(code: u16, scale: f32) -> Binding {
134 Binding {
135 code: InputCode::Key(code),
136 scale,
137 deadzone: 0.0,
138 }
139 }
140 #[test]
141 fn quick_tap_survives_no_simulation_and_edges_fire_once() {
142 let mut map = ActionMap::default();
143 let jump = map.action(vec![binding(32, 1.0)]);
144 map.feed(InputCode::Key(32), 1.0);
145 map.feed(InputCode::Key(32), 0.0);
146 assert_eq!(
147 map.consume_tick(jump),
148 Action {
149 value: 0.0,
150 pressed: true,
151 released: true
152 }
153 );
154 assert_eq!(map.consume_tick(jump), Action::default());
155 map.feed(InputCode::Key(32), 1.0);
156 map.lose_focus();
157 assert_eq!(map.peek(jump).value, 0.0);
158 }
159 #[test]
160 fn finite_extreme_scales_and_nonfinite_inputs_cannot_poison_actions() {
161 let mut map = ActionMap::default();
162 let id = map.action(vec![
163 binding(1, f32::MAX),
164 binding(1, f32::MAX),
165 binding(1, -f32::MAX),
166 binding(1, -f32::MAX),
167 ]);
168 map.feed(InputCode::Key(1), 1.0);
169 assert_eq!(map.peek(id).value, 0.0);
170 assert!(map.rebind(id, vec![binding(1, 1.0)]));
171 assert_eq!(map.peek(id).value, 1.0);
172 map.feed(InputCode::Key(1), f32::NAN);
173 assert_eq!(map.peek(id).value, 0.0);
174 }
175 #[test]
176 fn released_codes_free_capacity_and_registration_observes_held_input() {
177 let mut map = ActionMap::default();
178 for code in 0..1100 {
179 map.feed(InputCode::Key(code), 1.0);
180 map.feed(InputCode::Key(code), 0.0);
181 }
182 map.feed(InputCode::Key(1200), 1.0);
183 let id = map.action(vec![binding(1200, 1.0)]);
184 assert!(map.peek(id).pressed);
185 assert_eq!(map.peek(id).value, 1.0);
186 assert_eq!(map.values.len(), 1);
187 }
188 #[test]
189 fn rebind_and_handle_limits_are_transactional() {
190 let mut map = ActionMap::default();
191 let id = map.action(vec![binding(1, 1.0)]);
192 assert!(!map.rebind(id, vec![binding(2, 1.0); MAX_BINDINGS + 1]));
193 map.feed(InputCode::Key(1), 1.0);
194 assert_eq!(map.peek(id).value, 1.0);
195 assert!(
196 map.try_action(vec![binding(2, 1.0); MAX_BINDINGS + 1])
197 .is_none()
198 );
199 assert!(!map.rebind(999, vec![]));
200 assert_eq!(map.consume_tick(999), Action::default());
201 }
202}