1use crate::{ActionKind, ActionMap, ActionMapError};
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39pub enum Pattern {
40 Tap,
42 Hold,
44 DoubleTap,
47}
48
49impl Pattern {
50 pub const ALL: [Self; 3] = [Self::Tap, Self::Hold, Self::DoubleTap];
52
53 pub(crate) const fn index(self) -> usize {
55 match self {
56 Self::Tap => 0,
57 Self::Hold => 1,
58 Self::DoubleTap => 2,
59 }
60 }
61}
62
63impl ActionMap {
64 pub fn set_emits(
78 &mut self,
79 source: &str,
80 pattern: Pattern,
81 target: Option<&str>,
82 ) -> Result<(), ActionMapError> {
83 let Some(&source_idx) = self.name_to_idx.get(source) else {
84 return Err(ActionMapError::UnknownAction(source.to_owned()));
85 };
86 let target_idx = match target {
87 None => None,
88 Some(name) => {
89 let Some(&idx) = self.name_to_idx.get(name) else {
90 return Err(ActionMapError::UnknownAction(name.to_owned()));
91 };
92 if idx == source_idx {
93 return Err(ActionMapError::EmitsItself(source.to_owned()));
94 }
95 if self.slots[idx].kind() != ActionKind::Button {
96 return Err(ActionMapError::NotAButton(name.to_owned()));
97 }
98 Some(idx)
99 }
100 };
101 self.slots[source_idx].patterns.emits[pattern.index()] = target_idx;
102 Ok(())
103 }
104
105 #[must_use]
107 pub fn emits(&self, source: &str, pattern: Pattern) -> Option<&str> {
108 let &idx = self.name_to_idx.get(source)?;
109 let target = self.slots[idx].patterns.emits[pattern.index()]?;
110 Some(self.slots[target].decl.name.as_str())
111 }
112
113 pub(crate) fn drain_emits(&mut self) {
118 let mut next = 0;
119 while let Some(&target) = self.pending_emits.get(next) {
120 next += 1;
121 if !self.slots[target].emitted && self.is_live(target) {
122 self.slots[target].emitted = true;
123 self.resolve_slot(target);
124 }
125 }
126 self.pending_emits.clear();
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::{ActionDecl, ActionValue, Binding, ButtonState, Hold, InputTickState, Tap};
134 use crcbl_core::input::KeyCode;
135
136 const TICK: f32 = 0.0625;
138
139 const QUARTER: f32 = 0.25;
141
142 fn button(map: &mut ActionMap, name: &str, bindings: Vec<Binding>) {
143 map.declare(ActionDecl {
144 name: name.to_owned(),
145 kind: ActionKind::Button,
146 bindings,
147 });
148 }
149
150 fn hold(time: f32) -> Option<Hold> {
151 Some(Hold::new(time).expect("a runnable hold"))
152 }
153
154 fn charge_map() -> ActionMap {
156 let mut map = ActionMap::new();
157 button(&mut map, "jump", vec![Binding::Key(KeyCode::Space)]);
158 button(&mut map, "jump_charge", Vec::new());
159 map.set_hold("jump", hold(QUARTER)).expect("declared");
160 map.set_emits("jump", Pattern::Hold, Some("jump_charge"))
161 .expect("a button");
162 map
163 }
164
165 fn edges(
168 map: &mut ActionMap,
169 name: &str,
170 script: &[(u32, KeyCode, bool)],
171 ticks: u32,
172 ) -> Vec<(u32, bool, bool)> {
173 let mut seen = Vec::new();
174 for tick in 0..ticks {
175 map.begin_tick(TICK);
176 for &(_, key, down) in script.iter().filter(|(at, ..)| *at == tick) {
177 map.key_event(key, down);
178 }
179 let (pressed, released) = (map.just_pressed(name), map.just_released(name));
180 if pressed || released {
181 seen.push((tick, pressed, released));
182 }
183 }
184 seen
185 }
186
187 #[test]
191 fn a_hold_presses_the_action_it_emits_for_one_tick() {
192 let mut map = charge_map();
193 let mut seen = Vec::new();
194 for tick in 0..8 {
195 map.begin_tick(TICK);
196 if tick == 0 {
197 map.key_event(KeyCode::Space, true);
198 }
199 seen.push((
200 map.just_pressed("jump_charge"),
201 map.button_held("jump_charge"),
202 map.just_released("jump_charge"),
203 ));
204 assert_eq!(map.hold_fired("jump"), tick == 4, "tick {tick}");
205 }
206 let idle = (false, false, false);
207 assert_eq!(
208 seen,
209 [
210 idle,
211 idle,
212 idle,
213 idle,
214 (true, true, false),
215 (false, false, true),
216 idle,
217 idle
218 ]
219 );
220 }
221
222 #[test]
225 fn a_tap_presses_its_action_from_the_release() {
226 let mut map = ActionMap::new();
227 button(&mut map, "dash", vec![Binding::Key(KeyCode::KeyQ)]);
228 button(&mut map, "dash_tap", Vec::new());
229 map.set_tap("dash", Some(Tap::new(QUARTER).expect("runnable")))
230 .expect("declared");
231 map.set_emits("dash", Pattern::Tap, Some("dash_tap"))
232 .expect("a button");
233 let script = [(1, KeyCode::KeyQ, true), (2, KeyCode::KeyQ, false)];
234 assert_eq!(
235 edges(&mut map, "dash_tap", &script, 6),
236 [(2, true, false), (3, false, true)]
237 );
238 }
239
240 #[test]
245 fn the_edges_do_not_depend_on_declaration_order() {
246 let script = [(0, KeyCode::KeyA, true), (1, KeyCode::KeyB, true)];
247 let mut per_order = Vec::new();
248 for charge_first in [true, false] {
249 let mut map = ActionMap::new();
250 if charge_first {
251 button(&mut map, "charge", Vec::new());
252 }
253 button(&mut map, "a", vec![Binding::Key(KeyCode::KeyA)]);
254 button(&mut map, "b", vec![Binding::Key(KeyCode::KeyB)]);
255 if !charge_first {
256 button(&mut map, "charge", Vec::new());
257 }
258 for source in ["a", "b"] {
259 map.set_hold(source, hold(QUARTER)).expect("declared");
260 map.set_emits(source, Pattern::Hold, Some("charge"))
261 .expect("a button");
262 }
263 per_order.push(edges(&mut map, "charge", &script, 8));
264 }
265 assert_eq!(
266 per_order[0],
267 [(4, true, false), (5, true, true), (6, false, true)],
268 "declared first"
269 );
270 assert_eq!(per_order[1], per_order[0], "declared last");
271 }
272
273 #[test]
276 fn a_captured_tick_carries_the_emitted_press() {
277 let mut map = charge_map();
278 map.begin_tick(TICK);
279 map.key_event(KeyCode::Space, true);
280 for _ in 0..4 {
281 map.begin_tick(TICK);
282 }
283 let captured = InputTickState::capture(&map);
284 let Some(ActionValue::Button(charge)) = captured.get("jump_charge") else {
285 panic!("jump_charge is a captured button");
286 };
287 assert!(charge.just_pressed);
288 assert_eq!(charge.state, ButtonState::Pressed);
289 }
290
291 #[test]
294 fn a_disabled_emitted_action_is_not_pressed() {
295 let mut map = charge_map();
296 map.set_enabled("jump_charge", false);
297 let script = [(0, KeyCode::Space, true)];
298 assert_eq!(edges(&mut map, "jump_charge", &script, 8), []);
299 }
300
301 #[test]
302 fn set_emits_refuses_by_name_and_reads_back() {
303 let mut map = ActionMap::new();
304 button(&mut map, "jump", vec![Binding::Key(KeyCode::Space)]);
305 map.declare(ActionDecl {
306 name: "aim".to_owned(),
307 kind: ActionKind::Axis1,
308 bindings: Vec::new(),
309 });
310 button(&mut map, "charge", Vec::new());
311 let unknown = |name: &str| Err(ActionMapError::UnknownAction(name.to_owned()));
312 assert_eq!(
313 map.set_emits("nope", Pattern::Hold, Some("charge")),
314 unknown("nope")
315 );
316 assert_eq!(
317 map.set_emits("jump", Pattern::Hold, Some("nope")),
318 unknown("nope")
319 );
320 assert_eq!(
321 map.set_emits("jump", Pattern::Hold, Some("jump")),
322 Err(ActionMapError::EmitsItself("jump".to_owned()))
323 );
324 assert_eq!(
325 map.set_emits("jump", Pattern::Hold, Some("aim")),
326 Err(ActionMapError::NotAButton("aim".to_owned()))
327 );
328 assert_eq!(map.emits("jump", Pattern::Hold), None, "nothing stuck");
329
330 map.set_emits("jump", Pattern::DoubleTap, Some("charge"))
331 .expect("a button");
332 assert_eq!(map.emits("jump", Pattern::DoubleTap), Some("charge"));
333 assert_eq!(map.emits("jump", Pattern::Tap), None);
334 map.set_emits("jump", Pattern::DoubleTap, None)
335 .expect("declared");
336 assert_eq!(map.emits("jump", Pattern::DoubleTap), None);
337 }
338}