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