shuyi_ui/tick.rs
1//! A stage's rules in fixed steps (ADR-0003 ยง2): [`TickDriver`] steps a
2//! [`TickSim`] as many times as a frame has steps due
3//! ([`FrameInput::ticks`]), gives each step what the player did since the
4//! last one, and hands each step's events on once. [`ActionIds`] turns a
5//! game's action into the id a step's input holds, and back.
6//!
7//! A tick stage reads input from its steps' [`TickInput`], never from
8//! [`FrameInput::actions`]: frames and steps don't line up (a 120 Hz
9//! display runs a step every other frame; a late frame runs up to 4), so a
10//! press read straight from a frame would be lost on a frame with no step
11//! and seen again on a frame with several, and a key's repeats would count
12//! as presses.
13//!
14//! ```
15//! use shuyi_core::tick::TickRecord;
16//! use shuyi_core::toy::{Dot, DotEvent};
17//! use shuyi_ui::input::sample::Action;
18//! use shuyi_ui::screen::FrameInput;
19//! use shuyi_ui::tick::TickDriver;
20//! # let made = shuyi_core::Made::new("dot", shuyi_core::ContentHash(0));
21//!
22//! let mut dot = Dot::default();
23//! let mut driver = TickDriver::new().recording(TickRecord::new(made, 1));
24//! // A tap on a frame with no step due: kept for the next step.
25//! let tap = FrameInput::new(vec![Action::CursorRight], 0.004, vec![]);
26//! assert!(driver.update(&mut dot, &tap).is_empty());
27//! // Two steps due: the tap moves the dot in the first, and it stops in
28//! // the second.
29//! let late = FrameInput::new(vec![], 0.033, vec![]).with_ticks(2);
30//! let events = driver.update(&mut dot, &late);
31//! assert_eq!(events, [(2, DotEvent::Stopped { x: 1, y: 0 })]);
32//! assert_eq!(driver.record().map(TickRecord::len), Some(2));
33//! ```
34
35use crate::prelude::*;
36
37use shuyi_assets::keymap::ActionSet;
38use shuyi_core::tick::{ActionId, SeatInput, TickInput, TickRecord, TickSim};
39
40use crate::input::frame::FrameInput;
41
42/// A game's action as a step's input holds it ([`ActionId`]): its place
43/// in [`ActionSet::ALL`]. Every [`ActionSet`] has it.
44pub trait ActionIds: ActionSet {
45 /// Its id: its place in [`ActionSet::ALL`], from 0 (`u16::MAX` for an
46 /// action that isn't there, or past it).
47 fn id(self) -> ActionId;
48
49 /// The action whose id is `id`, if there is one.
50 fn from_id(id: ActionId) -> Option<Self>;
51}
52
53impl<A: ActionSet> ActionIds for A {
54 fn id(self) -> ActionId {
55 let place = Self::ALL.iter().position(|&action| action == self);
56 ActionId(
57 place
58 .and_then(|p| u16::try_from(p).ok())
59 .unwrap_or(u16::MAX),
60 )
61 }
62
63 fn from_id(id: ActionId) -> Option<Self> {
64 Self::ALL.get(usize::from(id.0)).copied()
65 }
66}
67
68/// An action going down or coming up, waiting for a step.
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70enum Edge {
71 /// It went down.
72 Press,
73 /// It came up.
74 Release,
75}
76
77/// What one seat did since the steps last took it: each press and release
78/// read and not yet given to a step, in order.
79#[derive(Debug, Clone)]
80struct Latch<A> {
81 /// The edges not given to a step yet, in the order they came.
82 queue: Vec<(A, Edge)>,
83 /// The actions whose last edge read is a press.
84 down: Vec<A>,
85 /// The actions whose last edge given to a step is a press.
86 given: Vec<A>,
87 /// The actions held at the end of the last frame read.
88 held: Vec<A>,
89}
90
91impl<A: ActionSet> Latch<A> {
92 fn new() -> Self {
93 Self {
94 queue: Vec::new(),
95 down: Vec::new(),
96 given: Vec::new(),
97 held: Vec::new(),
98 }
99 }
100
101 /// Reads one frame's `presses`, `released` actions and the actions
102 /// `held` at its end. An action's presses and releases alternate for
103 /// the steps: a press of an action already down (another of its keys)
104 /// and a release of one whose press no step saw (held since before the
105 /// stage) are dropped, and an action down but no longer held is let
106 /// go.
107 fn read(&mut self, presses: &[A], released: &[A], held: &[A]) {
108 // The actions this frame moved, in the order they came, then those
109 // down that it didn't name.
110 let mut actions: Vec<A> = Vec::new();
111 for &action in presses.iter().chain(released).chain(&self.down) {
112 if !actions.contains(&action) {
113 actions.push(action);
114 }
115 }
116 let mut lists: Vec<(A, Vec<Edge>)> = Vec::new();
117 for action in actions {
118 let edges = self.edges(action, presses, released, held);
119 if !edges.is_empty() {
120 lists.push((action, edges));
121 }
122 }
123 // Round by round, each action's first edge before any one's second,
124 // so presses keep their order across actions.
125 let rounds = lists.iter().map(|(_, edges)| edges.len()).max();
126 for round in 0..rounds.unwrap_or(0) {
127 for (action, edges) in &lists {
128 if let Some(&edge) = edges.get(round) {
129 self.queue.push((*action, edge));
130 }
131 }
132 }
133 self.held = held.to_vec();
134 }
135
136 /// `action`'s edges in a frame of `presses` and `released` that ends
137 /// with `held`, in order; it notes whether the action is down after
138 /// them.
139 fn edges(&mut self, action: A, presses: &[A], released: &[A], held: &[A]) -> Vec<Edge> {
140 let count = |list: &[A]| list.iter().filter(|&&a| a == action).count();
141 let mut ups = count(released);
142 let (was_held, is_held) = (self.held.contains(&action), held.contains(&action));
143 // The presses that put it down: one for each release, one more if
144 // it ends held and one less if it started held. Any other is
145 // another key of an action that was down already.
146 let mut downs = (ups + usize::from(is_held))
147 .saturating_sub(usize::from(was_held))
148 .min(count(presses));
149 let mut seen = self.down.contains(&action);
150 let mut down = was_held || seen;
151 let mut edges = Vec::new();
152 loop {
153 if down {
154 if ups == 0 {
155 break;
156 }
157 ups -= 1;
158 // A release of a press no step saw is dropped.
159 if core::mem::take(&mut seen) {
160 edges.push(Edge::Release);
161 }
162 } else {
163 if downs == 0 {
164 break;
165 }
166 downs -= 1;
167 edges.push(Edge::Press);
168 seen = true;
169 }
170 down = !down;
171 }
172 if seen && !is_held {
173 edges.push(Edge::Release);
174 seen = false;
175 }
176 self.down.retain(|&a| a != action);
177 if seen {
178 self.down.push(action);
179 }
180 edges
181 }
182
183 /// Gives one step its presses and releases, in order: for each action,
184 /// a release of a press an earlier step had, then a press. Whatever an
185 /// action has beyond waits for the next step, so nothing is lost: a
186 /// press always lands in a step before its release's, and an action
187 /// pressed again before a step shows its release and the press again
188 /// in the next.
189 fn step(&mut self) -> (Vec<A>, Vec<A>) {
190 let mut pressed: Vec<A> = Vec::new();
191 let mut released: Vec<A> = Vec::new();
192 let mut kept = Vec::new();
193 for (action, edge) in self.queue.drain(..) {
194 let pressed_now = pressed.contains(&action);
195 match edge {
196 Edge::Release if !pressed_now && !released.contains(&action) => {
197 released.push(action);
198 self.given.retain(|&a| a != action);
199 }
200 Edge::Press if !pressed_now => {
201 pressed.push(action);
202 self.given.push(action);
203 }
204 Edge::Release | Edge::Press => kept.push((action, edge)),
205 }
206 }
207 self.queue = kept;
208 (pressed, released)
209 }
210
211 /// Forgets every press and release no step has had yet. A press a step
212 /// had stays down, so its release still comes.
213 fn clear(&mut self) {
214 self.queue.clear();
215 self.down.clone_from(&self.given);
216 }
217}
218
219/// Runs a stage's [`TickSim`] in fixed steps: as many a frame as
220/// [`FrameInput::ticks`] says are due (counted by the platform's `App`, 60
221/// a second, at most 4 a frame). It keeps no clock of its own.
222///
223/// # Input reaches the steps
224///
225/// It keeps what each frame read until a step takes it, so the frame rate
226/// never changes which presses a simulation sees:
227///
228/// - **A press is pressed in one step**: the first run in or after the
229/// frame that read it, the first of a frame's steps when it has several.
230/// A frame that runs no step (less than a step's time passed, an action
231/// handed in by [`Game::act`](crate::Game::act), a slowed or stopped
232/// game) keeps it for the next.
233/// - **A release is released in one step**, after its press's: a tap
234/// between two steps is pressed in one and released in the next, so it
235/// is never lost.
236/// - **Held is the frame's**: every step a frame runs sees the actions
237/// held at its end. A tap is pressed without being held.
238/// - **Key repeats never reach a step** ([`FrameInput::presses`]).
239/// - **An action pressed twice before a step** isn't merged: the second
240/// press waits. The next step has the first press, the one after the
241/// release and the press again, and so on, so every press and release
242/// reaches a step, in order. An action is pressed when it goes down: a
243/// second key on an action already held presses nothing.
244/// - **Covered or resumed, it forgets.** A stage calls
245/// [`clear`](Self::clear) from [`Screen::covered`], and a frame after a
246/// suspend ([`FrameInput::resumed`]) clears it, so a press made before a
247/// pause screen or a suspend never fires after it. A press a step had
248/// stays down, and its release still comes.
249///
250/// Each step's input is one seat, seat 0, from the game's input. With a
251/// [`recording`](Self::recording) it is kept in a [`TickRecord`]: what each
252/// step saw, so the record replays the same whatever frame rate made it.
253///
254/// # Events are handed on once
255///
256/// [`update`](Self::update) hands on each step's events with the step's
257/// number (counted from 1), in order, a frame catching up included. It
258/// keeps the highest step it has handed on: after a rewind
259/// ([`rewind_to`](Self::rewind_to)), the steps that run again are stepped
260/// for their state, but their events aren't handed on a second time. A
261/// simulation asks for sound only through its events, so no sound plays
262/// twice.
263///
264/// [`Screen::covered`]: crate::Screen::covered
265#[derive(Debug, Clone)]
266pub struct TickDriver<A: ActionSet> {
267 latch: Latch<A>,
268 /// The steps run: the number of the last.
269 steps: u64,
270 /// The highest step whose events were handed on.
271 handed: u64,
272 /// What each step saw, while recording.
273 record: Option<TickRecord>,
274}
275
276impl<A: ActionSet> TickDriver<A> {
277 /// A driver that has run no step, and records nothing.
278 pub fn new() -> Self {
279 Self {
280 latch: Latch::new(),
281 steps: 0,
282 handed: 0,
283 record: None,
284 }
285 }
286
287 /// The same driver recording each step's input from now on into
288 /// `record` ([`record`](Self::record)).
289 #[must_use]
290 pub fn recording(mut self, record: TickRecord) -> Self {
291 self.record = Some(record);
292 self
293 }
294
295 /// Reads `input`'s presses and releases and runs `input.ticks()` steps
296 /// of `sim`, each with its seat's [`TickInput`] ([type docs](Self)).
297 /// Gives back the events of the steps not handed on before, each with
298 /// its step's number, in order.
299 pub fn update<S: TickSim>(
300 &mut self,
301 sim: &mut S,
302 input: &FrameInput<A>,
303 ) -> Vec<(u64, S::Event)> {
304 if input.resumed() {
305 self.latch.clear();
306 }
307 self.latch
308 .read(input.presses(), input.released(), input.held());
309 let mut handed = Vec::new();
310 for _ in 0..input.ticks() {
311 let (pressed, released) = self.latch.step();
312 let ids = |actions: &[A]| actions.iter().map(|&a| a.id()).collect();
313 let seat = SeatInput::new(ids(&pressed), ids(&self.latch.held), ids(&released));
314 let tick = TickInput::one(seat);
315 let events = sim.step(&tick);
316 if let Some(record) = &mut self.record {
317 record.push(tick);
318 }
319 self.steps += 1;
320 if self.steps > self.handed {
321 self.handed = self.steps;
322 let step = self.steps;
323 handed.extend(events.into_iter().map(|event| (step, event)));
324 }
325 }
326 handed
327 }
328
329 /// Forgets every press and release no step has had yet: from
330 /// [`Screen::covered`](crate::Screen::covered), so a press made just
331 /// before a pause screen never fires after it.
332 pub fn clear(&mut self) {
333 self.latch.clear();
334 }
335
336 /// Sets the steps run back to `step`, when the game has loaded its state
337 /// at that step (a rollback): the steps after it run again for their
338 /// state, and their events, handed on before, aren't handed on again.
339 /// The record forgets the steps after it. A `step` at or past the steps
340 /// run changes nothing.
341 pub fn rewind_to(&mut self, step: u64) {
342 if step >= self.steps {
343 return;
344 }
345 self.steps = step;
346 if let Some(record) = &mut self.record {
347 record.truncate(step);
348 }
349 }
350
351 /// The steps run: the number of the last one.
352 pub fn steps(&self) -> u64 {
353 self.steps
354 }
355
356 /// The highest step whose events were handed on.
357 pub fn handed(&self) -> u64 {
358 self.handed
359 }
360
361 /// The record of each step's input, while recording.
362 pub fn record(&self) -> Option<&TickRecord> {
363 self.record.as_ref()
364 }
365
366 /// Stops recording, handing back the record.
367 pub fn take_record(&mut self) -> Option<TickRecord> {
368 self.record.take()
369 }
370}
371
372impl<A: ActionSet> Default for TickDriver<A> {
373 fn default() -> Self {
374 Self::new()
375 }
376}
377
378#[cfg(test)]
379mod tests;