Skip to main content

crcbl_input/
emit.rs

1//! Patterns that emit a **named action**: a hold on `jump` that fires
2//! `jump_charge`, as the input plan's binding sketch spelled it
3//! (`Hold(400, "jump_charge")`, `docs/notes/simulation.md`).
4//!
5//! A pattern firing is an edge on the action it is attached to —
6//! [`ActionMap::hold_fired`] and the rest read it there. [`ActionMap::set_emits`]
7//! also names a **button** action the pattern presses when it fires, so the
8//! game, a replay and the server read `jump_charge` as an action like any
9//! other — and [`InputTickState::capture`](crate::InputTickState::capture)
10//! carries it, which a pattern edge on its own never was.
11//!
12//! # What an emitted press looks like
13//!
14//! The emitted action goes down on the tick the pattern fires — with
15//! [`ActionMap::just_pressed`] — and comes up at the next
16//! [`ActionMap::begin_tick`], with [`ActionMap::just_released`]. A pattern that
17//! fires as a tick begins (a hold, or a single tap that waited out its double
18//! tap's window) presses it for that tick; one that fires on an event (a tap's
19//! release, a double tap's second press) presses it from that event to the
20//! next tick. Its own bindings still drive it as well, and an action already
21//! down — held by a binding, or pressed by another pattern this tick — is not
22//! pressed again.
23//!
24//! **The order actions were declared in does not change the answer.** Presses
25//! raised while `begin_tick` re-resolves every action are applied after the
26//! last one is resolved, so an emitted action declared before its pattern's
27//! action and one declared after see the same edges on the same ticks.
28//!
29//! A disabled emitted action, or one in a context that is not active, is not
30//! pressed: the emission obeys the same rules its bindings do.
31
32use crate::{ActionKind, ActionMap, ActionMapError};
33
34/// One of the patterns [`ActionMap::set_emits`] can make emit an action.
35///
36/// The repeat pattern is not one: it pulses on a schedule while held, which is
37/// a rate rather than a gesture, and nothing has asked for it to emit.
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39pub enum Pattern {
40    /// The [`Tap`](crate::Tap) [`ActionMap::set_tap`] attaches.
41    Tap,
42    /// The [`Hold`](crate::Hold) [`ActionMap::set_hold`] attaches.
43    Hold,
44    /// The [`DoubleTap`](crate::DoubleTap) [`ActionMap::set_double_tap`]
45    /// attaches.
46    DoubleTap,
47}
48
49impl Pattern {
50    /// Every pattern, in the order an action's patterns are listed in.
51    pub const ALL: [Self; 3] = [Self::Tap, Self::Hold, Self::DoubleTap];
52
53    /// This pattern's position in [`Pattern::ALL`].
54    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    /// Make an action's pattern press `target` whenever it fires, or stop it
65    /// with `None` — see the module docs for what the press looks like.
66    ///
67    /// Names the emitted action whether or not the pattern is attached yet:
68    /// [`ActionMap::set_hold`] and the rest leave it alone, and a pattern that
69    /// is not attached never fires. Nothing in flight is cancelled, since the
70    /// pattern's timing does not change.
71    ///
72    /// # Errors
73    /// [`ActionMapError::UnknownAction`] if `source` or `target` is not
74    /// declared, [`ActionMapError::EmitsItself`] if they are the same action,
75    /// [`ActionMapError::NotAButton`] if `target` is not an
76    /// [`ActionKind::Button`] — a press is the only thing a pattern can give.
77    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    /// The action an action's pattern emits, if it names one.
106    #[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    /// Press every action a pattern queued since this was last called — see
114    /// the module docs. Each press can fire the pressed action's own patterns
115    /// and queue more; an action is pressed at most once between two ticks,
116    /// which is what ends the chain.
117    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    /// Sixteen ticks a second, exact in binary, as `patterns.rs`'s tests use.
137    const TICK: f32 = 0.0625;
138
139    /// Four ticks.
140    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    /// A map with `jump` on Space carrying a hold that emits `jump_charge`.
155    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    /// The edges `name` showed on each of `ticks` ticks, pressing and
166    /// releasing keys just after the tick begins as `edges` says.
167    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    /// **A hold presses its named action on the tick it fires**, and the
188    /// action comes up on the next — while the hold is still an edge on the
189    /// action it is attached to.
190    #[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    /// A tap fires on the release event, and presses its action from there
223    /// to the next tick.
224    #[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    /// **Declaration order does not change the edges.** Two holds a tick apart
241    /// press one action on consecutive ticks; the second press is a fresh
242    /// press whether the emitted action was declared before its sources or
243    /// after them.
244    #[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    /// The press reaches a captured tick, which a pattern edge alone never
274    /// did: the server sees `jump_charge` go down.
275    #[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    /// A disabled emitted action is not pressed, as a disabled action's
292    /// bindings press nothing.
293    #[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}