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