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}