Skip to main content

euv_engine/scheduler/
impl.rs

1use super::*;
2
3/// Implements default configuration and state initialization for scheduler types.
4impl Default for SchedulerConfig {
5    fn default() -> SchedulerConfig {
6        SchedulerConfig::new(DEFAULT_FIXED_TIMESTEP, DEFAULT_MAX_FRAME_TIME)
7    }
8}
9
10/// Implements `Default` for `SchedulerState` as a freshly created stopped state.
11impl Default for SchedulerState {
12    fn default() -> SchedulerState {
13        SchedulerState::new(UNINITIALIZED_TIME)
14    }
15}
16
17/// Implements time retrieval and tick execution for `SchedulerState`.
18impl SchedulerState {
19    /// Returns the current high-resolution timestamp in seconds from `performance.now()`.
20    ///
21    /// Falls back to `0.0` when the global window or the `performance.now`
22    /// API is unavailable (for example outside a browser window context).
23    ///
24    /// # Returns
25    ///
26    /// - `f64` - The current time in seconds, or `0.0` when unavailable.
27    pub fn current_time() -> f64 {
28        let Some(window_value) = window() else {
29            return 0.0;
30        };
31        let Ok(performance) = Reflect::get(
32            window_value.as_ref(),
33            &JsValue::from_str(PERFORMANCE_OBJECT),
34        ) else {
35            return 0.0;
36        };
37        let Ok(now_value) = Reflect::get(&performance, &JsValue::from_str(PERFORMANCE_NOW_METHOD))
38        else {
39            return 0.0;
40        };
41        let Some(now_millis) = now_value.as_f64() else {
42            return 0.0;
43        };
44        now_millis / 1000.0
45    }
46
47    /// Performs one tick of the fixed-timestep scheduler.
48    ///
49    /// Calculates the elapsed frame time, clamps it to `max_frame_time`, accumulates it,
50    /// then runs as many fixed updates as needed. Finally, computes the interpolation
51    /// factor and calls the render callback.
52    ///
53    /// # Arguments
54    ///
55    /// - `&SchedulerConfig` - The scheduler configuration.
56    /// - `&TickHandlerRc` - The handler receiving update and render callbacks.
57    pub fn tick(&mut self, config: &SchedulerConfig, handler: &TickHandlerRc) {
58        let current_time: f64 = Self::current_time();
59        let frame_time: f64 = if self.get_last_time() == UNINITIALIZED_TIME {
60            config.get_fixed_timestep()
61        } else {
62            current_time - self.get_last_time()
63        };
64        self.set_last_time(current_time);
65        let clamped_frame_time: f64 = frame_time.min(config.get_max_frame_time());
66        *self.get_mut_accumulator() += clamped_frame_time;
67        while self.get_accumulator() >= config.get_fixed_timestep() {
68            handler.get_mut().on_update(config.get_fixed_timestep());
69            *self.get_mut_accumulator() -= config.get_fixed_timestep();
70            *self.get_mut_update_count() += 1;
71        }
72        let interpolation: f64 = self.get_accumulator() / config.get_fixed_timestep();
73        handler.get_mut().on_render(interpolation);
74        *self.get_mut_frame_count() += 1;
75    }
76}
77
78/// Implements lifecycle management for `SchedulerHandle`.
79impl SchedulerHandle {
80    /// Stops the scheduler and cancels any pending animation frame request.
81    pub fn stop(&self) {
82        let state: &mut SchedulerState = self.get_state().get_mut();
83        state.set_running(false);
84        if let Some(id) = state.get_mut_raf_id().take() {
85            let Some(window_value) = window() else {
86                // Drop the closure so the box can be collected.
87                let _ = self.get_closure_cell().try_take();
88                return;
89            };
90            let _: Result<(), JsValue> = window_value.cancel_animation_frame(id);
91        }
92        // Drop the closure so the box can be collected.
93        let _ = self.get_closure_cell().try_take();
94    }
95
96    /// Returns whether the scheduler is currently running.
97    ///
98    /// # Returns
99    ///
100    /// - `bool` - True if the scheduler is running.
101    pub fn is_running(&self) -> bool {
102        // SAFETY: caller contract - no mutable access to the same
103        // SchedulerState can be alive alongside this call.
104        self.get_state().get().get_running()
105    }
106
107    /// Returns the total number of fixed update steps executed.
108    ///
109    /// # Returns
110    ///
111    /// - `u64` - The update count.
112    pub fn update_count(&self) -> u64 {
113        self.get_state().get().get_update_count()
114    }
115
116    /// Returns the total number of render frames executed.
117    ///
118    /// # Returns
119    ///
120    /// - `u64` - The frame count.
121    pub fn frame_count(&self) -> u64 {
122        self.get_state().get().get_frame_count()
123    }
124
125    /// Starts the scheduler with the given configuration and handler.
126    ///
127    /// Creates a `requestAnimationFrame`-driven loop that calls `tick`
128    /// on each animation frame. The returned `SchedulerHandle` can be used to stop the scheduler.
129    ///
130    /// When no global window exists (non-browser context), the scheduler is
131    /// not started and an already-stopped handle is returned instead.
132    ///
133    /// # Arguments
134    ///
135    /// - `SchedulerConfig` - The scheduler configuration.
136    /// - `TickHandlerRc` - The handler receiving update and render callbacks.
137    ///
138    /// # Returns
139    ///
140    /// - `SchedulerHandle` - A handle to control the running scheduler.
141    pub fn start(config: SchedulerConfig, handler: TickHandlerRc) -> SchedulerHandle {
142        let state: Rc<EngineCell<SchedulerState>> =
143            Rc::new(EngineCell::new(SchedulerState::new(UNINITIALIZED_TIME)));
144        let closure_cell: RafClosureCell = Rc::new(MaybeEngineCell::new());
145        // Install the initial closure once spawn starts.
146        let state_ref_init: &mut SchedulerState = state.get_mut();
147        state_ref_init.set_running(true);
148        let state_clone: Rc<EngineCell<SchedulerState>> = state.clone();
149        let closure_cell_clone: RafClosureCell = closure_cell.clone();
150        let handler_clone: TickHandlerRc = handler.clone();
151        let raf_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
152            {
153                let state_ref: &mut SchedulerState = state_clone.get_mut();
154                if !state_ref.get_running() {
155                    return;
156                }
157                state_ref.tick(&config, &handler_clone);
158            }
159            let state_ro: &SchedulerState = state_clone.get();
160            if state_ro.get_running() {
161                let Some(window_value) = window() else {
162                    return;
163                };
164                let cell: RafClosureCell = closure_cell_clone.clone();
165                let Some(raf_closure) = cell.try_get() else {
166                    return;
167                };
168                let id: i32 = window_value
169                    .request_animation_frame(raf_closure.as_ref().unchecked_ref())
170                    .unwrap_or(0);
171                let state_ref_id: &mut SchedulerState = state_clone.get_mut();
172                state_ref_id.set_raf_id(Some(id));
173            }
174        }));
175        let Some(window_value) = window() else {
176            // No window context: install the closure, mark the scheduler
177            // stopped, and return an inert handle.
178            let state_ref_stop: &mut SchedulerState = state.get_mut();
179            state_ref_stop.set_running(false);
180            let _ = closure_cell.try_set(raf_closure);
181            return SchedulerHandle::new(state, closure_cell);
182        };
183        let id: i32 = window_value
184            .request_animation_frame(raf_closure.as_ref().unchecked_ref())
185            .unwrap_or(0);
186        let state_ref_id: &mut SchedulerState = state.get_mut();
187        state_ref_id.set_raf_id(Some(id));
188        let _ = closure_cell.try_set(raf_closure);
189        SchedulerHandle::new(state, closure_cell)
190    }
191}