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    /// 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        *self.frame_waker.write().unwrap() = Some(Arc::new(waker));
57    }
58
59    #[cfg(target_arch = "wasm32")]
60    pub fn set_frame_waker(&self, waker: impl Fn() + Send + 'static) {
61        *self.frame_waker.write().unwrap() = Some(Arc::new(SyncWaker(Box::new(waker))));
62    }
63
64    /// Clears any registered frame waker.
65    pub fn clear_frame_waker(&self) {
66        *self.frame_waker.write().unwrap() = None;
67    }
68
69    #[cfg(not(target_arch = "wasm32"))]
70    fn wake(&self) {
71        let waker = self.frame_waker.read().unwrap().clone();
72        if let Some(waker) = waker {
73            waker();
74        }
75    }
76
77    #[cfg(target_arch = "wasm32")]
78    fn wake(&self) {
79        let waker = self.frame_waker.read().unwrap().clone();
80        if let Some(waker) = waker {
81            waker.call();
82        }
83    }
84}
85
86impl Default for StdScheduler {
87    fn default() -> Self {
88        Self::new()
89    }
90}
91
92impl fmt::Debug for StdScheduler {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.debug_struct("StdScheduler")
95            .field(
96                "frame_requested",
97                &self.frame_requested.load(Ordering::SeqCst),
98            )
99            .finish()
100    }
101}
102
103impl RuntimeScheduler for StdScheduler {
104    fn schedule_frame(&self) {
105        self.frame_requested.store(true, Ordering::SeqCst);
106        self.wake();
107    }
108}
109
110/// Clock implementation backed by [`std::time`].
111#[derive(Debug, Default, Clone)]
112pub struct StdClock;
113
114impl Clock for StdClock {
115    type Instant = Instant;
116
117    fn now(&self) -> Self::Instant {
118        Instant::now()
119    }
120
121    fn elapsed_millis(&self, since: Self::Instant) -> u64 {
122        since.elapsed().as_millis() as u64
123    }
124}
125
126impl StdClock {
127    /// Returns the elapsed time as a [`Duration`] for convenience.
128    pub fn elapsed(&self, since: Instant) -> Duration {
129        since.elapsed()
130    }
131}
132
133/// Convenience container bundling the standard scheduler and clock.
134#[derive(Clone)]
135pub struct StdRuntime {
136    scheduler: Arc<StdScheduler>,
137    clock: Arc<StdClock>,
138    runtime: Runtime,
139}
140
141impl StdRuntime {
142    /// Creates a new standard runtime instance.
143    pub fn new() -> Self {
144        let scheduler = Arc::new(StdScheduler::default());
145        let runtime = Runtime::new(scheduler.clone());
146        Self {
147            scheduler,
148            clock: Arc::new(StdClock),
149            runtime,
150        }
151    }
152
153    /// Returns a [`cranpose_core::Runtime`] configured with the standard scheduler.
154    pub fn runtime(&self) -> Runtime {
155        self.runtime.clone()
156    }
157
158    /// Returns a handle to the runtime.
159    pub fn runtime_handle(&self) -> RuntimeHandle {
160        self.runtime.handle()
161    }
162
163    /// Returns the runtime's frame clock.
164    #[cfg(feature = "internal")]
165    pub fn frame_clock(&self) -> FrameClock {
166        self.runtime.frame_clock()
167    }
168
169    /// Returns the scheduler implementation.
170    pub fn scheduler(&self) -> Arc<StdScheduler> {
171        Arc::clone(&self.scheduler)
172    }
173
174    /// Returns the clock implementation.
175    pub fn clock(&self) -> Arc<StdClock> {
176        Arc::clone(&self.clock)
177    }
178
179    /// Returns whether a frame was requested since the last poll.
180    pub fn take_frame_request(&self) -> bool {
181        self.scheduler.take_frame_request()
182    }
183
184    /// Registers a waker to be called when the runtime schedules a new frame.
185    #[cfg(not(target_arch = "wasm32"))]
186    pub fn set_frame_waker(&self, waker: impl Fn() + Send + Sync + 'static) {
187        self.scheduler.set_frame_waker(waker);
188    }
189
190    #[cfg(target_arch = "wasm32")]
191    pub fn set_frame_waker(&self, waker: impl Fn() + Send + 'static) {
192        self.scheduler.set_frame_waker(waker);
193    }
194
195    /// Clears any previously registered frame waker.
196    pub fn clear_frame_waker(&self) {
197        self.scheduler.clear_frame_waker();
198    }
199
200    /// Drains pending frame callbacks using the provided frame timestamp in nanoseconds.
201    pub fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
202        self.runtime_handle()
203            .drain_frame_callbacks(frame_time_nanos);
204    }
205}
206
207impl fmt::Debug for StdRuntime {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        f.debug_struct("StdRuntime")
210            .field("scheduler", &self.scheduler)
211            .field("clock", &self.clock)
212            .finish()
213    }
214}
215
216impl Default for StdRuntime {
217    fn default() -> Self {
218        Self::new()
219    }
220}
221
222#[cfg(test)]
223#[path = "tests/std_runtime_tests.rs"]
224mod tests;