Skip to main content

oxframe_core/
executor.rs

1use std::future::Future;
2use std::rc::Rc;
3use std::task::{Context, Waker};
4
5use oxframe_protocol::Meta;
6
7use crate::flow::{Task, TaskState};
8use crate::runtime::{self, Runtime};
9
10/// Deterministic single-threaded executor: one [`step`](Executor::step) is
11/// one frame. The root future and every spawned task are polled once per
12/// step, in a fixed order (root, then tasks in spawn order). No wakers, no
13/// clocks, no threads — the same input always produces the same frames.
14pub struct Executor {
15    rt: Rc<Runtime>,
16    root: Option<Task>,
17    tasks: Vec<Task>,
18    next_frame: u32,
19}
20
21impl Executor {
22    /// `seed` feeds the scene's random generator ([`random`](crate::random)).
23    pub fn new(fps: f64, meta: Meta, seed: u64, root: impl Future<Output = ()> + 'static) -> Self {
24        Self {
25            rt: Runtime::new(fps, meta, seed),
26            root: Some(Task {
27                fut: Box::pin(root),
28                time: 0.0,
29                state: Rc::new(TaskState {
30                    finished: Default::default(),
31                    cancelled: Default::default(),
32                }),
33            }),
34            tasks: Vec::new(),
35            next_frame: 0,
36        }
37    }
38
39    /// Runs `f` with this executor's runtime installed, so that `now()`,
40    /// `tune!` and friends work outside of [`step`](Executor::step).
41    pub fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
42        let _guard = runtime::install(&self.rt);
43        f()
44    }
45
46    /// Computes the next frame. Returns `true` if the scene is finished
47    /// (its root future completed during this or an earlier step).
48    pub fn step(&mut self) -> bool {
49        let Some(root) = self.root.as_mut() else {
50            return true;
51        };
52        let _guard = runtime::install(&self.rt);
53        self.rt.frame.set(self.next_frame);
54        let mut cx = Context::from_waker(Waker::noop());
55
56        let root_done = root.poll(&mut cx);
57        self.tasks.retain_mut(|t| !t.poll(&mut cx));
58        // Tasks spawned during this frame start in this frame.
59        loop {
60            let new: Vec<Task> = std::mem::take(&mut *self.rt.spawned.borrow_mut());
61            if new.is_empty() {
62                break;
63            }
64            for mut t in new {
65                if !t.poll(&mut cx) {
66                    self.tasks.push(t);
67                }
68            }
69        }
70
71        self.next_frame += 1;
72        if root_done {
73            self.root = None;
74            self.tasks.clear();
75        }
76        root_done
77    }
78
79    /// Whether the scene's root task has completed.
80    pub fn is_finished(&self) -> bool {
81        self.root.is_none()
82    }
83
84    /// Number of frames computed so far; the last computed frame is
85    /// `frames_done() - 1`.
86    pub fn frames_done(&self) -> u32 {
87        self.next_frame
88    }
89
90    /// Frames per second.
91    pub fn fps(&self) -> f64 {
92        self.rt.fps
93    }
94
95    /// Current markers, eases and tunables, including defaults recorded so far.
96    pub fn meta(&self) -> Meta {
97        self.rt.meta.borrow().clone()
98    }
99
100    /// True if scene code recorded new defaults into the meta since the
101    /// last call.
102    pub fn take_meta_changed(&self) -> bool {
103        self.rt.meta_changed.replace(false)
104    }
105
106    /// The scene before this one exists, so a transition has something to blend.
107    pub fn set_has_previous(&self, has_previous: bool) {
108        self.rt.has_previous.set(has_previous);
109    }
110
111    /// Transition declared while the scene ran, if any.
112    pub fn transition(&self) -> Option<crate::transition::Recorded> {
113        *self.rt.transition.borrow()
114    }
115
116    /// Sound effects the scene scheduled, in the order it scheduled them.
117    pub fn sounds(&self) -> Vec<crate::SoundClip> {
118        self.rt.sounds.borrow().clone()
119    }
120
121    /// Spoken lines the scene scheduled, in the order it scheduled them.
122    pub fn voices(&self) -> Vec<crate::VoiceLine> {
123        self.rt.voices.borrow().clone()
124    }
125
126    /// Stops and notes the scene recorded, in the order it recorded them.
127    pub fn slides(&self) -> Vec<crate::SlideMark> {
128        self.rt.slides.borrow().clone()
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use std::cell::RefCell;
135
136    use super::*;
137    use crate::{
138        Signal, all, any, loop_while, next_frame, presets, repeat, sequence, spawn, stagger,
139        wait_for, wait_until,
140    };
141
142    /// Runs to completion, sampling `f` after each frame.
143    fn run<T>(fps: f64, fut: impl Future<Output = ()> + 'static, f: impl Fn() -> T) -> Vec<T> {
144        let mut ex = Executor::new(fps, Meta::default(), 0, fut);
145        let mut out = Vec::new();
146        for _ in 0..10_000 {
147            let done = ex.step();
148            out.push(f());
149            if done {
150                return out;
151            }
152        }
153        panic!("scene did not finish");
154    }
155
156    #[test]
157    fn tween_takes_exact_frames_and_hits_target() {
158        let s = Signal::new(0.0f64);
159        let s2 = s.clone();
160        let values = run(
161            60.0,
162            async move { s2.to(1.0, 1.0).ease(presets::LINEAR).await },
163            || s.get(),
164        );
165        // Frames 0..=60: t = 0, 1/60, ..., 1.
166        assert_eq!(values.len(), 61);
167        assert_eq!(values[0], 0.0);
168        assert!((values[30] - 0.5).abs() < 1e-12);
169        assert_eq!(values[60], 1.0);
170    }
171
172    #[test]
173    fn chained_waits_do_not_drift() {
174        // 0.1 s is not a multiple of 1/24; 30 of them must still end at 3 s.
175        let frames = run(
176            24.0,
177            async {
178                for _ in 0..30 {
179                    wait_for(0.1).await;
180                }
181            },
182            || (),
183        );
184        assert_eq!(frames.len(), 3 * 24 + 1);
185    }
186
187    #[test]
188    fn all_waits_for_longest_and_continues_from_its_end() {
189        let log = Rc::new(RefCell::new(Vec::new()));
190        let l = log.clone();
191        run(
192            10.0,
193            async move {
194                all((wait_for(0.5), wait_for(1.0), async {})).await;
195                l.borrow_mut().push((crate::frame(), crate::time()));
196            },
197            || (),
198        );
199        assert_eq!(*log.borrow(), vec![(10, 1.0)]);
200    }
201
202    #[test]
203    fn sequence_staggers_starts() {
204        let a = Signal::new(0.0f64);
205        let b = Signal::new(0.0f64);
206        let (a2, b2) = (a.clone(), b.clone());
207        let values = run(
208            10.0,
209            async move {
210                sequence(
211                    0.5,
212                    [
213                        a2.to(1.0, 1.0).ease(presets::LINEAR),
214                        b2.to(1.0, 1.0).ease(presets::LINEAR),
215                    ],
216                )
217                .await;
218            },
219            || (a.get(), b.get()),
220        );
221        assert_eq!(values.len(), 16);
222        assert!((values[5].0 - 0.5).abs() < 1e-12);
223        assert_eq!(values[5].1, 0.0);
224        assert!((values[10].1 - 0.5).abs() < 1e-12);
225    }
226
227    #[test]
228    fn spawn_runs_in_background_and_join_waits() {
229        let s = Signal::new(0.0f64);
230        let s2 = s.clone();
231        let values = run(
232            10.0,
233            async move {
234                let h = spawn(async move { s2.to(1.0, 0.5).ease(presets::LINEAR).await });
235                next_frame().await;
236                h.await;
237            },
238            || s.get(),
239        );
240        assert!((values[2] - 0.4).abs() < 1e-12);
241        assert_eq!(*values.last().unwrap(), 1.0);
242    }
243
244    #[test]
245    fn wait_until_records_default_and_reads_meta() {
246        let mut ex = Executor::new(10.0, Meta::default(), 0, async {
247            wait_for(0.3).await;
248            wait_until("beat").await;
249        });
250        while !ex.step() {}
251        assert!((ex.meta().markers["beat"] - 0.3).abs() < 1e-12);
252        assert!(ex.take_meta_changed());
253
254        let mut meta = Meta::default();
255        meta.markers.insert("beat".into(), 1.0);
256        let mut ex = Executor::new(10.0, meta, 0, async {
257            wait_for(0.3).await;
258            wait_until("beat").await;
259        });
260        while !ex.step() {}
261        assert_eq!(ex.frames_done(), 11);
262        assert!(!ex.take_meta_changed());
263    }
264
265    #[test]
266    fn random_is_seeded() {
267        let draw = |seed| {
268            let out = Rc::new(RefCell::new(Vec::new()));
269            let o = out.clone();
270            let mut ex = Executor::new(10.0, Meta::default(), seed, async move {
271                for _ in 0..4 {
272                    o.borrow_mut().push(crate::random());
273                }
274            });
275            ex.step();
276            out.take()
277        };
278        assert_eq!(draw(1), draw(1));
279        assert_ne!(draw(1), draw(2));
280        assert!(draw(3).iter().all(|v| (0.0..1.0).contains(v)));
281    }
282
283    /// `(frame, time)` when the scene reaches the logging point.
284    fn end_of(fps: f64, fut: impl Future<Output = ()> + 'static) -> (u32, f64) {
285        let log = Rc::new(RefCell::new(None));
286        let l = log.clone();
287        run(
288            fps,
289            async move {
290                fut.await;
291                *l.borrow_mut() = Some((crate::frame(), crate::time()));
292            },
293            || (),
294        );
295        log.borrow().expect("logged")
296    }
297
298    #[test]
299    fn all_takes_vecs_arrays_and_iterators() {
300        let durations = [0.5, 1.0, 0.2];
301        assert_eq!(
302            end_of(10.0, async move {
303                all(durations.map(wait_for)).await;
304            })
305            .1,
306            1.0
307        );
308        assert_eq!(
309            end_of(10.0, async move {
310                all(durations.iter().map(|&d| wait_for(d))).await;
311            })
312            .1,
313            1.0
314        );
315        assert_eq!(
316            end_of(10.0, async move {
317                all(vec![wait_for(0.3), wait_for(0.7)]).await;
318            })
319            .1,
320            0.7
321        );
322        // Outputs come back in order.
323        let out = Rc::new(RefCell::new(Vec::new()));
324        let o = out.clone();
325        end_of(10.0, async move {
326            *o.borrow_mut() = all((1..=3).map(|i| async move { i * 10 })).await;
327        });
328        assert_eq!(*out.borrow(), vec![10, 20, 30]);
329        // Nothing to wait for completes at once.
330        assert_eq!(
331            end_of(10.0, async {
332                all(Vec::<crate::flow::WaitFor>::new()).await;
333            })
334            .1,
335            0.0
336        );
337    }
338
339    #[test]
340    fn any_completes_with_the_first_and_continues_from_its_end() {
341        let s = Signal::new(0.0f64);
342        let s2 = s.clone();
343        let first = Rc::new(RefCell::new(None));
344        let f = first.clone();
345        let (frame, time) = end_of(10.0, async move {
346            let i = any((
347                wait_for(2.0),
348                s2.to(1.0, 2.0).ease(presets::LINEAR),
349                wait_for(0.5),
350            ))
351            .await;
352            *f.borrow_mut() = Some(i);
353        });
354        assert_eq!(*first.borrow(), Some(2));
355        assert_eq!((frame, time), (5, 0.5));
356        // The slower tween stopped where it was.
357        assert!((s.get() - 0.25).abs() < 1e-12, "{}", s.get());
358    }
359
360    #[test]
361    fn stagger_starts_each_item_later() {
362        let signals: Vec<Signal<f64>> = (0..3).map(|_| Signal::new(0.0)).collect();
363        let watch = signals.clone();
364        let values = run(
365            10.0,
366            async move {
367                stagger(0.2, &signals, |s| s.to(1.0, 1.0).ease(presets::LINEAR)).await;
368            },
369            move || watch.iter().map(Signal::get).collect::<Vec<_>>(),
370        );
371        // Last item starts at 0.4 s and ends at 1.4 s.
372        assert_eq!(values.len(), 15);
373        let at_half = &values[5];
374        assert!((at_half[0] - 0.5).abs() < 1e-12);
375        assert!((at_half[1] - 0.3).abs() < 1e-12);
376        assert!((at_half[2] - 0.1).abs() < 1e-12);
377    }
378
379    #[test]
380    fn repeat_and_loop_while() {
381        assert_eq!(end_of(10.0, repeat(3, || wait_for(0.5))).1, 1.5);
382        let left = Rc::new(std::cell::Cell::new(4));
383        let l = left.clone();
384        let end = end_of(
385            10.0,
386            loop_while(
387                move || {
388                    l.set(l.get() - 1);
389                    l.get() >= 0
390                },
391                || wait_for(0.25),
392            ),
393        );
394        assert_eq!(end.1, 1.0);
395        assert_eq!(left.get(), -1);
396    }
397}