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
107#[cfg(test)]
108mod tests {
109    use std::cell::RefCell;
110
111    use super::*;
112    use crate::{Signal, all, next_frame, presets, sequence, spawn, wait_for, wait_until};
113
114    /// Runs to completion, sampling `f` after each frame.
115    fn run<T>(fps: f64, fut: impl Future<Output = ()> + 'static, f: impl Fn() -> T) -> Vec<T> {
116        let mut ex = Executor::new(fps, Meta::default(), 0, fut);
117        let mut out = Vec::new();
118        for _ in 0..10_000 {
119            let done = ex.step();
120            out.push(f());
121            if done {
122                return out;
123            }
124        }
125        panic!("scene did not finish");
126    }
127
128    #[test]
129    fn tween_takes_exact_frames_and_hits_target() {
130        let s = Signal::new(0.0f64);
131        let s2 = s.clone();
132        let values = run(
133            60.0,
134            async move { s2.to(1.0, 1.0).ease(presets::LINEAR).await },
135            || s.get(),
136        );
137        // Frames 0..=60: t = 0, 1/60, ..., 1.
138        assert_eq!(values.len(), 61);
139        assert_eq!(values[0], 0.0);
140        assert!((values[30] - 0.5).abs() < 1e-12);
141        assert_eq!(values[60], 1.0);
142    }
143
144    #[test]
145    fn chained_waits_do_not_drift() {
146        // 0.1 s is not a multiple of 1/24; 30 of them must still end at 3 s.
147        let frames = run(
148            24.0,
149            async {
150                for _ in 0..30 {
151                    wait_for(0.1).await;
152                }
153            },
154            || (),
155        );
156        assert_eq!(frames.len(), 3 * 24 + 1);
157    }
158
159    #[test]
160    fn all_waits_for_longest_and_continues_from_its_end() {
161        let log = Rc::new(RefCell::new(Vec::new()));
162        let l = log.clone();
163        run(
164            10.0,
165            async move {
166                all((wait_for(0.5), wait_for(1.0), async {})).await;
167                l.borrow_mut().push((crate::frame(), crate::time()));
168            },
169            || (),
170        );
171        assert_eq!(*log.borrow(), vec![(10, 1.0)]);
172    }
173
174    #[test]
175    fn sequence_staggers_starts() {
176        let a = Signal::new(0.0f64);
177        let b = Signal::new(0.0f64);
178        let (a2, b2) = (a.clone(), b.clone());
179        let values = run(
180            10.0,
181            async move {
182                sequence(
183                    0.5,
184                    vec![
185                        Box::pin(a2.to(1.0, 1.0).ease(presets::LINEAR)),
186                        Box::pin(b2.to(1.0, 1.0).ease(presets::LINEAR)),
187                    ],
188                )
189                .await;
190            },
191            || (a.get(), b.get()),
192        );
193        assert_eq!(values.len(), 16);
194        assert!((values[5].0 - 0.5).abs() < 1e-12);
195        assert_eq!(values[5].1, 0.0);
196        assert!((values[10].1 - 0.5).abs() < 1e-12);
197    }
198
199    #[test]
200    fn spawn_runs_in_background_and_join_waits() {
201        let s = Signal::new(0.0f64);
202        let s2 = s.clone();
203        let values = run(
204            10.0,
205            async move {
206                let h = spawn(async move { s2.to(1.0, 0.5).ease(presets::LINEAR).await });
207                next_frame().await;
208                h.await;
209            },
210            || s.get(),
211        );
212        assert!((values[2] - 0.4).abs() < 1e-12);
213        assert_eq!(*values.last().unwrap(), 1.0);
214    }
215
216    #[test]
217    fn wait_until_records_default_and_reads_meta() {
218        let mut ex = Executor::new(10.0, Meta::default(), 0, async {
219            wait_for(0.3).await;
220            wait_until("beat").await;
221        });
222        while !ex.step() {}
223        assert!((ex.meta().markers["beat"] - 0.3).abs() < 1e-12);
224        assert!(ex.take_meta_changed());
225
226        let mut meta = Meta::default();
227        meta.markers.insert("beat".into(), 1.0);
228        let mut ex = Executor::new(10.0, meta, 0, async {
229            wait_for(0.3).await;
230            wait_until("beat").await;
231        });
232        while !ex.step() {}
233        assert_eq!(ex.frames_done(), 11);
234        assert!(!ex.take_meta_changed());
235    }
236
237    #[test]
238    fn random_is_seeded() {
239        let draw = |seed| {
240            let out = Rc::new(RefCell::new(Vec::new()));
241            let o = out.clone();
242            let mut ex = Executor::new(10.0, Meta::default(), seed, async move {
243                for _ in 0..4 {
244                    o.borrow_mut().push(crate::random());
245                }
246            });
247            ex.step();
248            out.take()
249        };
250        assert_eq!(draw(1), draw(1));
251        assert_ne!(draw(1), draw(2));
252        assert!(draw(3).iter().all(|v| (0.0..1.0).contains(v)));
253    }
254}