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