Skip to main content

compose_rust/
runtime.rs

1//! The seam between the boundary and whatever builds the tree.
2//!
3//! The boundary entry points, the batch arena, the theme and window records, assets,
4//! messages, notifications, streaming appends and the frame request all belong to this
5//! crate and behave the same whatever authoring model produced the tree. What differs is
6//! how a tree is built and changed: a Dioxus `VirtualDom` diffs one, a recomposition
7//! runtime re-runs the scopes that read a changed state. That part is a [`Runtime`].
8//!
9//! An application registers one with [`LaunchBuilder::launch_runtime`], and every
10//! `compose_rust_host_*` call is answered by a [`Host`] that drives it. Nothing about
11//! the boundary changes with the runtime: the same five entry points, the same records,
12//! the same batch.
13//!
14//! [`LaunchBuilder::launch_runtime`]: crate::LaunchBuilder::launch_runtime
15//! [`Host`]: crate::Host
16
17use crate::protocol::{BatchEncoder, HostEvent, Mutation, ProtocolError};
18use crate::schema::{AssetKind, MessageDuration, Selection, Theme, Window};
19use std::task::{Context, Poll};
20
21/// One batch, written by the runtime and by the Host into the same arena.
22///
23/// The Host opens it with [`Batch::begin`], writes the records that are its own (the theme,
24/// the window, assets, messages, notifications, streamed tails) and has the runtime write
25/// the tree's records between them, then closes it with [`Batch::finish`]. The bytes that
26/// come back are what the boundary hands the Renderer.
27///
28/// A runtime has no fallible way to report a bad record from the middle of a diff, so
29/// [`Batch::fail`] keeps the error and `finish` returns it in place of the batch.
30pub struct Batch {
31    encoder: BatchEncoder,
32    error: Option<ProtocolError>,
33}
34
35impl Default for Batch {
36    fn default() -> Self {
37        Self::new()
38    }
39}
40
41impl Batch {
42    /// Sized so that an ordinary screen's batches never grow it: once warm, writing a
43    /// frame allocates nothing.
44    pub fn new() -> Self {
45        Self {
46            encoder: BatchEncoder::with_capacity(16 * 1024, 4 * 1024, 256),
47            error: None,
48        }
49    }
50
51    /// The batch arena, so a Renderer that reads Host memory through a mapped view can be
52    /// handed one.
53    pub fn arena(&self) -> (*const u8, usize) {
54        self.encoder.arena()
55    }
56
57    /// Starts a new batch, discarding the previous one and any error it carried.
58    pub fn begin(&mut self) {
59        self.encoder.clear();
60        self.error = None;
61    }
62
63    /// Closes the batch and returns its bytes, or the first error written into it.
64    pub fn finish(&mut self) -> Result<&[u8], ProtocolError> {
65        if let Some(error) = self.error.take() {
66            return Err(error);
67        }
68        self.encoder.finish()
69    }
70
71    /// Writes one record. After an error nothing more is written: the batch is already
72    /// going to be refused.
73    pub fn write(&mut self, mutation: Mutation<'_>) {
74        if self.error.is_none() {
75            if let Err(error) = self.encoder.encode(&mutation) {
76                self.error = Some(error);
77            }
78        }
79    }
80
81    /// Records an error found while building the batch. `finish` returns it.
82    pub fn fail(&mut self, error: ProtocolError) {
83        self.error = Some(error);
84    }
85
86    /// The root theme record. Written once per rebuild, never per frame.
87    pub fn set_theme(&mut self, theme: Theme) {
88        self.write(Mutation::SetTheme(theme));
89    }
90
91    /// The root window record. Written once per rebuild, never per frame.
92    pub fn set_window(&mut self, window: Window) {
93        self.write(Mutation::SetWindow(window));
94    }
95
96    pub fn set_text_node(&mut self, node_id: u32, text: &str, selection: Option<Selection>) {
97        self.write(Mutation::SetText {
98            node_id,
99            text,
100            selection,
101        });
102    }
103
104    /// Copies one asset into the batch. The bytes ride behind the records, and the
105    /// Renderer takes its own copy inside the call that carries them.
106    pub fn register_asset(&mut self, asset_id: u32, kind: AssetKind, bytes: &[u8]) {
107        self.write(Mutation::RegisterAsset {
108            asset_id,
109            kind,
110            bytes,
111        });
112    }
113
114    pub fn release_asset(&mut self, asset_id: u32) {
115        self.write(Mutation::ReleaseAsset { asset_id });
116    }
117
118    /// Writes one transient message into the batch.
119    ///
120    /// It names no node, because a message is not in the tree: it is a sentence with a
121    /// lifetime, and that lifetime belongs to the Renderer.
122    pub fn show_message(
123        &mut self,
124        handler_id: u64,
125        text: &str,
126        action: &str,
127        duration: MessageDuration,
128    ) {
129        self.write(Mutation::ShowMessage {
130            handler_id,
131            text,
132            action,
133            duration,
134        });
135    }
136
137    /// Writes one notification command into the batch.
138    ///
139    /// Like a message it names no node: what it asks for happens outside the window, and
140    /// the Renderer is the side that owns the platform it happens on.
141    pub(crate) fn notification(&mut self, command: &crate::notification::NotificationCommand) {
142        use crate::notification::NotificationCommand;
143        match command {
144            NotificationCommand::Post(notification) => {
145                self.write(Mutation::PostNotification {
146                    key: &notification.key,
147                    title: &notification.title,
148                    body: &notification.body,
149                    channel: &notification.channel,
150                    action_1: &notification.actions[0],
151                    action_2: &notification.actions[1],
152                    importance: notification.importance,
153                    presentation: notification.presentation,
154                });
155            }
156            NotificationCommand::Withdraw(key) => {
157                self.write(Mutation::WithdrawNotification { key });
158            }
159            NotificationCommand::RequestPermission => {
160                self.write(Mutation::RequestNotificationPermission);
161            }
162        }
163    }
164
165    /// Appends the streamed tail to a Text node without resending its whole value.
166    pub fn append_text_node(&mut self, node_id: u32, text: &str) {
167        self.write(Mutation::AppendText { node_id, text });
168    }
169}
170
171/// What builds and changes the tree, seen from the boundary.
172///
173/// The Host owns everything that is not the tree and calls in here for the rest. Every
174/// method runs on the Renderer's UI thread, inside a boundary call, between a
175/// [`Batch::begin`] and a [`Batch::finish`] the Host makes on [`Runtime::batch_mut`], so a
176/// runtime writes its records and returns; it never opens or closes a batch itself.
177pub trait Runtime {
178    /// The batch this runtime writes the tree's records into. The Host writes its own
179    /// records into the same one, before and after the runtime's.
180    fn batch(&self) -> &Batch;
181
182    fn batch_mut(&mut self) -> &mut Batch;
183
184    /// Writes the whole tree. Called once on a fresh runtime, which is also what a resync
185    /// gets: the Host makes a new runtime and rebuilds it.
186    fn rebuild(&mut self);
187
188    /// Writes whatever changed since the last call: state a worker wrote, a scope that a
189    /// size class or a design system woke, the consequences of an event.
190    fn render(&mut self);
191
192    /// Runs the handler an event names, and nothing else: the Host renders afterwards.
193    ///
194    /// Returns the value the boundary hands back as the call's result, which is how a key
195    /// handler says it consumed the key. An event naming a handler or a node this runtime
196    /// does not have is an error, and so is a payload that no handler can receive.
197    fn handle_event(&mut self, event: &HostEvent<'_>) -> Result<i64, ProtocolError>;
198
199    /// The application's name for a node it asked to have measured, if it asked.
200    fn size_token(&self, node_id: u32) -> Option<u32> {
201        let _ = node_id;
202        None
203    }
204
205    /// Whether the runtime has work waiting that a frame should run. A runtime that is
206    /// woken by something outside a boundary call (a timer, a task it spawned) wakes the
207    /// context's waker, and the Host turns that into a frame request.
208    fn poll_work(&mut self, context: &mut Context<'_>) -> Poll<()> {
209        let _ = context;
210        Poll::Pending
211    }
212
213    /// Runs code the application handed to the Host, a message's action for one, inside
214    /// whatever context this runtime's state writes need.
215    fn run_in_context(&mut self, action: &mut dyn FnMut()) {
216        action();
217    }
218}