Skip to main content

cranpose_runtime_std/
lib.rs

1//! Standard runtime services backed by Rust's `std` library.
2//!
3//! This crate provides concrete implementations of the platform
4//! abstraction traits defined in `cranpose-core`. Applications can
5//! construct a [`StdRuntime`] and pass it to [`cranpose_core::Composition`]
6//! to power the runtime with `std` primitives.
7
8#![deny(unsafe_code)]
9
10#[cfg(target_arch = "wasm32")]
11use std::cell::RefCell;
12use std::fmt;
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::sync::Arc;
15#[cfg(not(target_arch = "wasm32"))]
16use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
17use std::time::{Duration, Instant};
18
19#[cfg(feature = "internal")]
20use cranpose_core::internal::FrameClock;
21use cranpose_core::{Clock, Runtime, RuntimeHandle, RuntimeScheduler};
22
23#[cfg(not(target_arch = "wasm32"))]
24type NativeFrameWaker = Arc<dyn Fn() + Send + Sync + 'static>;
25
26/// Scheduler that delegates work to Rust's threading primitives.
27pub struct StdScheduler {
28    frame_requested: AtomicBool,
29    #[cfg(not(target_arch = "wasm32"))]
30    frame_waker: RwLock<Option<NativeFrameWaker>>,
31    #[cfg(target_arch = "wasm32")]
32    frame_waker: RefCell<Option<Box<dyn Fn() + 'static>>>,
33}
34
35impl StdScheduler {
36    pub fn new() -> Self {
37        Self {
38            frame_requested: AtomicBool::new(false),
39            frame_waker: Default::default(),
40        }
41    }
42
43    /// Returns whether a frame has been requested since the last call.
44    pub fn take_frame_request(&self) -> bool {
45        self.frame_requested.swap(false, Ordering::SeqCst)
46    }
47
48    /// Returns whether a frame is currently pending without consuming the request.
49    pub fn has_frame_request(&self) -> bool {
50        self.frame_requested.load(Ordering::SeqCst)
51    }
52
53    /// Registers a waker that will be invoked whenever a new frame is scheduled.
54    #[cfg(not(target_arch = "wasm32"))]
55    pub fn set_frame_waker(&self, waker: impl Fn() + Send + Sync + 'static) {
56        let old_waker = {
57            let mut frame_waker = self.frame_waker_write();
58            frame_waker.replace(Arc::new(waker))
59        };
60        drop(old_waker);
61    }
62
63    #[cfg(target_arch = "wasm32")]
64    pub fn set_frame_waker(&self, waker: impl Fn() + 'static) {
65        *self.frame_waker.borrow_mut() = Some(Box::new(waker));
66    }
67
68    /// Clears any registered frame waker.
69    #[cfg(not(target_arch = "wasm32"))]
70    pub fn clear_frame_waker(&self) {
71        let old_waker = {
72            let mut frame_waker = self.frame_waker_write();
73            frame_waker.take()
74        };
75        drop(old_waker);
76    }
77
78    /// Clears any registered frame waker.
79    #[cfg(target_arch = "wasm32")]
80    pub fn clear_frame_waker(&self) {
81        *self.frame_waker.borrow_mut() = None;
82    }
83
84    #[cfg(not(target_arch = "wasm32"))]
85    fn wake(&self) {
86        let waker = self.frame_waker_read().clone();
87        if let Some(waker) = waker {
88            waker();
89        }
90    }
91
92    #[cfg(target_arch = "wasm32")]
93    fn wake(&self) {
94        if let Some(waker) = self.frame_waker.borrow().as_ref() {
95            waker();
96        }
97    }
98
99    #[cfg(not(target_arch = "wasm32"))]
100    fn frame_waker_read(&self) -> RwLockReadGuard<'_, Option<NativeFrameWaker>> {
101        match self.frame_waker.read() {
102            Ok(guard) => guard,
103            Err(poisoned) => poisoned.into_inner(),
104        }
105    }
106
107    #[cfg(not(target_arch = "wasm32"))]
108    fn frame_waker_write(&self) -> RwLockWriteGuard<'_, Option<NativeFrameWaker>> {
109        match self.frame_waker.write() {
110            Ok(guard) => guard,
111            Err(poisoned) => poisoned.into_inner(),
112        }
113    }
114}
115
116impl Default for StdScheduler {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122impl fmt::Debug for StdScheduler {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        f.debug_struct("StdScheduler")
125            .field(
126                "frame_requested",
127                &self.frame_requested.load(Ordering::SeqCst),
128            )
129            .finish()
130    }
131}
132
133impl RuntimeScheduler for StdScheduler {
134    fn schedule_frame(&self) {
135        self.frame_requested.store(true, Ordering::SeqCst);
136        self.wake();
137    }
138}
139
140/// Clock implementation backed by [`std::time`].
141#[derive(Debug, Default, Clone)]
142pub struct StdClock;
143
144impl Clock for StdClock {
145    type Instant = Instant;
146
147    fn now(&self) -> Self::Instant {
148        Instant::now()
149    }
150
151    fn elapsed_millis(&self, since: Self::Instant) -> u64 {
152        since.elapsed().as_millis() as u64
153    }
154}
155
156impl StdClock {
157    /// Returns the elapsed time as a [`Duration`] for convenience.
158    pub fn elapsed(&self, since: Instant) -> Duration {
159        since.elapsed()
160    }
161}
162
163/// Convenience container bundling the standard scheduler and clock.
164#[derive(Clone)]
165pub struct StdRuntime {
166    scheduler: Arc<StdScheduler>,
167    clock: Arc<StdClock>,
168    runtime: Runtime,
169}
170
171impl StdRuntime {
172    /// Creates a new standard runtime instance.
173    pub fn new() -> Self {
174        let scheduler = Arc::new(StdScheduler::default());
175        let runtime = Runtime::new(scheduler.clone());
176        Self {
177            scheduler,
178            clock: Arc::new(StdClock),
179            runtime,
180        }
181    }
182
183    /// Returns a [`cranpose_core::Runtime`] configured with the standard scheduler.
184    pub fn runtime(&self) -> Runtime {
185        self.runtime.clone()
186    }
187
188    /// Returns a handle to the runtime.
189    pub fn runtime_handle(&self) -> RuntimeHandle {
190        self.runtime.handle()
191    }
192
193    /// Returns the runtime's frame clock.
194    #[cfg(feature = "internal")]
195    pub fn frame_clock(&self) -> FrameClock {
196        self.runtime.frame_clock()
197    }
198
199    /// Returns the scheduler implementation.
200    pub fn scheduler(&self) -> Arc<StdScheduler> {
201        Arc::clone(&self.scheduler)
202    }
203
204    /// Returns the clock implementation.
205    pub fn clock(&self) -> Arc<StdClock> {
206        Arc::clone(&self.clock)
207    }
208
209    /// Returns whether a frame was requested since the last poll.
210    pub fn take_frame_request(&self) -> bool {
211        self.scheduler.take_frame_request()
212    }
213
214    pub fn has_frame_request(&self) -> bool {
215        self.scheduler.has_frame_request()
216    }
217
218    /// Registers a waker to be called when the runtime schedules a new frame.
219    #[cfg(not(target_arch = "wasm32"))]
220    pub fn set_frame_waker(&self, waker: impl Fn() + Send + Sync + 'static) {
221        self.scheduler.set_frame_waker(waker);
222    }
223
224    #[cfg(target_arch = "wasm32")]
225    pub fn set_frame_waker(&self, waker: impl Fn() + 'static) {
226        self.scheduler.set_frame_waker(waker);
227    }
228
229    /// Clears any previously registered frame waker.
230    pub fn clear_frame_waker(&self) {
231        self.scheduler.clear_frame_waker();
232    }
233
234    /// Drains pending frame callbacks using the provided frame timestamp in nanoseconds.
235    pub fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
236        self.runtime_handle()
237            .drain_frame_callbacks(frame_time_nanos);
238    }
239}
240
241impl fmt::Debug for StdRuntime {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        f.debug_struct("StdRuntime")
244            .field("scheduler", &self.scheduler)
245            .field("clock", &self.clock)
246            .finish()
247    }
248}
249
250impl Default for StdRuntime {
251    fn default() -> Self {
252        Self::new()
253    }
254}
255
256#[cfg(test)]
257#[path = "tests/std_runtime_tests.rs"]
258mod tests;