Skip to main content

ravenrs_widgets/
remote.rs

1//! Asking for a frame from another thread. Everything else in the runtime
2//! belongs to one thread, so this is the one handle that may leave it.
3
4use std::fmt;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::{Arc, OnceLock};
7
8type Notify = Box<dyn Fn() + Send + Sync>;
9
10#[derive(Default)]
11struct Shared {
12    /// Set by [`UiWaker::wake`] and cleared by the build it causes.
13    dirty: AtomicBool,
14    /// How the platform gets out of its sleep; absent in tests and on the web.
15    notify: OnceLock<Notify>,
16}
17
18/// Rebuilds the UI from any thread, when background work has something to show.
19///
20/// Get one with [`BuildCx::ui_waker`](crate::BuildCx::ui_waker) and move it to
21/// the thread. Send the result through a channel and call [`UiWaker::wake`]; the
22/// next build runs on the UI thread and reads the channel. Between wakes the app
23/// stays asleep, so there is nothing to poll.
24///
25/// ```
26/// use std::sync::mpsc::channel;
27/// use ravenrs_widgets::{BuildCx, NodeId, View};
28///
29/// struct Pump;
30///
31/// impl View for Pump {
32///     fn build(&self, cx: &mut BuildCx<'_>) -> NodeId {
33///         let waker = cx.ui_waker();
34///         let (send, _receive) = channel();
35///         std::thread::spawn(move || {
36///             let _ = send.send(21 * 2);
37///             waker.wake();
38///         });
39///         cx.empty()
40///     }
41/// }
42/// ```
43///
44/// The web build has no other threads to wake it from; there, change a
45/// [`State`](ravenrs_reactive::State) as usual.
46#[derive(Clone, Default)]
47#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
48pub struct UiWaker(Arc<Shared>);
49
50impl fmt::Debug for UiWaker {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        f.debug_struct("UiWaker").field("dirty", &self.is_dirty()).finish()
53    }
54}
55
56#[cfg_attr(feature = "live", ravenrs_live::expose)]
57impl UiWaker {
58    /// Asks for a rebuild. Calls made before it happens count as one.
59    pub fn wake(&self) {
60        self.0.dirty.store(true, Ordering::Release);
61        if let Some(notify) = self.0.notify.get() {
62            notify();
63        }
64    }
65
66    pub(crate) fn is_dirty(&self) -> bool {
67        self.0.dirty.load(Ordering::Acquire)
68    }
69
70    pub(crate) fn clear(&self) {
71        self.0.dirty.store(false, Ordering::Release);
72    }
73
74    pub(crate) fn set_notify(&self, notify: Notify) {
75        if self.0.notify.set(notify).is_err() {
76            tracing::warn!("the UI waker was already connected to a platform");
77        }
78    }
79}