1use super::{ActionMap, ActionMapError, ActionValue, ButtonState};
25
26pub const REPEAT_DELAY: f32 = 0.5;
32
33pub const REPEAT_INTERVAL: f32 = 0.1;
39
40#[derive(Clone, Copy, Debug, PartialEq)]
47pub struct Repeat {
48 delay: f32,
49 interval: f32,
50}
51
52impl Repeat {
53 pub const UI: Self = Self {
56 delay: REPEAT_DELAY,
57 interval: REPEAT_INTERVAL,
58 };
59
60 #[must_use]
63 pub fn new(delay: f32, interval: f32) -> Option<Self> {
64 let valid = delay.is_finite() && delay >= 0.0 && interval.is_finite() && interval > 0.0;
65 valid.then_some(Self { delay, interval })
66 }
67
68 #[must_use]
70 pub const fn delay(self) -> f32 {
71 self.delay
72 }
73
74 #[must_use]
76 pub const fn interval(self) -> f32 {
77 self.interval
78 }
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
84pub enum Cardinal {
85 Up,
87 Down,
89 Left,
91 Right,
93}
94
95impl Cardinal {
96 #[must_use]
99 pub fn of(x: f32, y: f32) -> Option<Self> {
100 if x == 0.0 && y == 0.0 {
101 None
102 } else if x.abs() > y.abs() {
103 Some(if x > 0.0 { Self::Right } else { Self::Left })
104 } else {
105 Some(if y > 0.0 { Self::Up } else { Self::Down })
106 }
107 }
108}
109
110#[derive(Clone, Copy, Debug, PartialEq, Eq)]
112pub(crate) enum Held {
113 Down,
114 Positive,
115 Negative,
116 Toward(Cardinal),
117}
118
119impl Held {
120 pub(crate) fn of(value: &ActionValue) -> Option<Self> {
121 match value {
122 ActionValue::Button(button) => matches!(
123 button.state,
124 ButtonState::Pressed | ButtonState::Held { .. }
125 )
126 .then_some(Self::Down),
127 ActionValue::Axis1(axis) if axis.value > 0.0 => Some(Self::Positive),
128 ActionValue::Axis1(axis) if axis.value < 0.0 => Some(Self::Negative),
129 ActionValue::Axis1(_) => None,
130 ActionValue::Axis2(axis) => Cardinal::of(axis.x, axis.y).map(Self::Toward),
131 }
132 }
133}
134
135#[derive(Clone, Debug)]
137pub(crate) struct RepeatState {
138 pattern: Repeat,
139 held: Option<Held>,
140 next_at: f64,
142 pub(crate) fired: bool,
144}
145
146impl RepeatState {
147 pub(crate) fn reset(&mut self) {
148 self.held = None;
149 self.fired = false;
150 }
151
152 pub(crate) fn update(&mut self, value: &ActionValue, elapsed: f64) {
154 let now = Held::of(value);
155 if now.is_none() {
156 self.held = None;
157 return;
158 }
159 if now != self.held {
160 self.held = now;
161 self.fired = true;
162 self.next_at = elapsed + f64::from(self.pattern.delay);
163 return;
164 }
165 if elapsed >= self.next_at {
166 self.fired = true;
167 let interval = f64::from(self.pattern.interval);
169 let missed = ((elapsed - self.next_at) / interval).floor() + 1.0;
170 self.next_at += missed * interval;
171 if self.next_at <= elapsed {
172 self.next_at += interval;
173 }
174 }
175 }
176}
177
178impl ActionMap {
179 pub fn set_repeat(&mut self, name: &str, repeat: Option<Repeat>) -> Result<(), ActionMapError> {
188 let Some(&idx) = self.name_to_idx.get(name) else {
189 return Err(ActionMapError::UnknownAction(name.to_owned()));
190 };
191 let elapsed = self.elapsed;
192 let slot = &mut self.slots[idx];
193 slot.repeat = repeat.map(|pattern| RepeatState {
194 pattern,
195 held: Held::of(&slot.value),
196 next_at: elapsed + f64::from(pattern.delay),
197 fired: false,
198 });
199 Ok(())
200 }
201
202 #[must_use]
204 pub fn repeat(&self, name: &str) -> Option<Repeat> {
205 let &idx = self.name_to_idx.get(name)?;
206 self.slots[idx].repeat.as_ref().map(|state| state.pattern)
207 }
208
209 #[must_use]
213 pub fn repeated(&self, name: &str) -> bool {
214 self.name_to_idx
215 .get(name)
216 .and_then(|&idx| self.slots[idx].repeat.as_ref())
217 .is_some_and(|state| state.fired)
218 }
219
220 #[must_use]
223 pub fn cardinal(&self, name: &str) -> Option<Cardinal> {
224 let (x, y) = self.axis2(name);
225 Cardinal::of(x, y)
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use crate::{ActionDecl, ActionKind, Binding};
233 use crcbl_core::input::KeyCode;
234
235 const TICK: f32 = 0.05;
242
243 fn map_with_repeat(kind: ActionKind, bindings: Vec<Binding>) -> ActionMap {
244 let mut map = ActionMap::new();
245 map.declare(ActionDecl {
246 name: "nav".to_owned(),
247 kind,
248 bindings,
249 });
250 map.set_repeat("nav", Some(Repeat::UI)).expect("declared");
251 map
252 }
253
254 fn pulses(map: &mut ActionMap, press: impl FnOnce(&mut ActionMap), ticks: u32) -> Vec<u32> {
257 let mut fired = Vec::new();
258 map.begin_tick(TICK);
259 press(map);
260 for tick in 0..ticks {
261 if tick > 0 {
262 map.begin_tick(TICK);
263 }
264 if map.repeated("nav") {
265 fired.push(tick);
266 }
267 }
268 fired
269 }
270
271 #[test]
274 fn a_held_button_repeats_after_the_delay_then_at_the_interval() {
275 let mut map = map_with_repeat(ActionKind::Button, vec![Binding::Key(KeyCode::Enter)]);
276 let fired = pulses(&mut map, |map| map.key_event(KeyCode::Enter, true), 17);
277 assert_eq!(fired, [0, 10, 12, 14, 16]);
279 }
280
281 #[test]
284 fn the_schedule_holds_at_sixty_hertz() {
285 let mut map = map_with_repeat(ActionKind::Button, vec![Binding::Key(KeyCode::Enter)]);
286 let mut fired = Vec::new();
287 map.begin_tick(1.0 / 60.0);
288 map.key_event(KeyCode::Enter, true);
289 for tick in 0..50 {
290 if tick > 0 {
291 map.begin_tick(1.0 / 60.0);
292 }
293 if map.repeated("nav") {
294 fired.push(tick);
295 }
296 }
297 assert_eq!(fired, [0, 30, 36, 42, 48]);
298 }
299
300 #[test]
302 fn a_release_ends_the_schedule() {
303 let mut map = map_with_repeat(ActionKind::Button, vec![Binding::Key(KeyCode::Enter)]);
304 let _ = pulses(&mut map, |map| map.key_event(KeyCode::Enter, true), 8);
305 map.key_event(KeyCode::Enter, false);
306 let fired = pulses(&mut map, |map| map.key_event(KeyCode::Enter, true), 11);
307 assert_eq!(
308 fired,
309 [0, 10],
310 "a fresh delay, not the old schedule's remainder"
311 );
312 }
313
314 #[test]
317 fn a_long_tick_pulses_once_and_leaves_no_backlog() {
318 let mut map = map_with_repeat(ActionKind::Button, vec![Binding::Key(KeyCode::Enter)]);
319 map.begin_tick(TICK);
320 map.key_event(KeyCode::Enter, true);
321 map.begin_tick(2.03);
324 assert!(map.repeated("nav"), "the long tick pulses");
325 map.begin_tick(TICK);
326 assert!(
327 !map.repeated("nav"),
328 "2.08 s: the missed repeats are gone rather than queued",
329 );
330 map.begin_tick(TICK);
331 assert!(map.repeated("nav"), "2.13 s: and the schedule carries on");
332 }
333
334 #[test]
337 fn a_direction_change_restarts_the_schedule() {
338 let wasd = Binding::Wasd {
339 up: KeyCode::KeyW,
340 down: KeyCode::KeyS,
341 left: KeyCode::KeyA,
342 right: KeyCode::KeyD,
343 };
344 let mut map = map_with_repeat(ActionKind::Axis2, vec![wasd]);
345 let _ = pulses(&mut map, |map| map.key_event(KeyCode::KeyD, true), 6);
346 assert_eq!(map.cardinal("nav"), Some(Cardinal::Right));
347
348 map.begin_tick(TICK);
351 map.key_event(KeyCode::KeyS, true);
352 assert_eq!(
353 map.cardinal("nav"),
354 Some(Cardinal::Down),
355 "a diagonal's tie goes to the vertical",
356 );
357 assert!(map.repeated("nav"), "turning is a new press");
358 let mut fired = Vec::new();
359 for tick in 1..11 {
360 map.begin_tick(TICK);
361 if map.repeated("nav") {
362 fired.push(tick);
363 }
364 }
365 assert_eq!(fired, [10], "the delay restarted at the turn");
366 }
367
368 #[test]
370 fn a_one_dimensional_axis_repeats_by_sign() {
371 let mut map = map_with_repeat(
372 ActionKind::Axis1,
373 vec![Binding::KeyAxis {
374 negative: KeyCode::KeyQ,
375 positive: KeyCode::KeyE,
376 }],
377 );
378 map.begin_tick(TICK);
379 map.key_event(KeyCode::KeyE, true);
380 assert!(map.repeated("nav"));
381 map.begin_tick(TICK);
382 map.key_event(KeyCode::KeyE, false);
383 map.key_event(KeyCode::KeyQ, true);
384 assert!(map.repeated("nav"), "the other sign is another press");
385 }
386
387 #[test]
389 fn only_runnable_schedules_exist() {
390 assert_eq!(Repeat::new(0.5, 0.1), Some(Repeat::UI));
391 assert!(Repeat::new(0.0, 0.1).is_some(), "no delay is a schedule");
392 for (delay, interval) in [
393 (0.5, 0.0),
394 (0.5, -1.0),
395 (-0.1, 0.1),
396 (f32::NAN, 0.1),
397 (0.5, f32::INFINITY),
398 ] {
399 assert_eq!(Repeat::new(delay, interval), None, "({delay}, {interval})");
400 }
401 }
402
403 #[test]
405 fn attaching_does_not_pulse_and_no_schedule_never_does() {
406 let mut map = ActionMap::new();
407 map.declare(ActionDecl {
408 name: "nav".to_owned(),
409 kind: ActionKind::Button,
410 bindings: vec![Binding::Key(KeyCode::Enter)],
411 });
412 map.key_event(KeyCode::Enter, true);
413 assert!(!map.repeated("nav"), "no schedule");
414 map.set_repeat("nav", Some(Repeat::UI)).expect("declared");
415 assert!(!map.repeated("nav"));
416 assert_eq!(map.repeat("nav"), Some(Repeat::UI));
417 assert_eq!(
418 map.set_repeat("nope", None),
419 Err(ActionMapError::UnknownAction("nope".to_owned())),
420 );
421 }
422}