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