compose_rust/runtime/seam.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: ¬ification.key,
147 title: ¬ification.title,
148 body: ¬ification.body,
149 channel: ¬ification.channel,
150 action_1: ¬ification.actions[0],
151 action_2: ¬ification.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 /// Writes one edit the application asked of a code editor: replace `range` of the
166 /// document as it stood at `base_version` with `text`. The Renderer moves it past what
167 /// the reader typed since, or refuses it and reports `request_id` back.
168 pub fn edit_code(
169 &mut self,
170 node_id: u32,
171 request_id: u32,
172 base_version: u32,
173 range: crate::code::CodeRange,
174 text: &str,
175 ) {
176 self.write(Mutation::EditCode {
177 node_id,
178 request_id,
179 base_version,
180 start_line: range.start.line,
181 start_column: range.start.column,
182 end_line: range.end.line,
183 end_column: range.end.column,
184 text,
185 });
186 }
187
188 /// Writes one request to put a code editor's selection between two places in the
189 /// document as it stood at `base_version`. The Renderer moves both ends past what the
190 /// reader typed since and drops the request, with a protocol error, where an end is not
191 /// a place in the document.
192 pub fn set_selection(
193 &mut self,
194 node_id: u32,
195 base_version: u32,
196 anchor: crate::code::Position,
197 active: crate::code::Position,
198 ) {
199 self.write(Mutation::SetSelection {
200 node_id,
201 base_version,
202 anchor_line: anchor.line,
203 anchor_column: anchor.column,
204 active_line: active.line,
205 active_column: active.column,
206 });
207 }
208
209 /// Writes one request to scroll a code editor until `range`, as the document stood at
210 /// `base_version`, is on screen. A range inside a folded stretch unfolds it.
211 pub fn reveal_range(
212 &mut self,
213 node_id: u32,
214 base_version: u32,
215 range: crate::code::CodeRange,
216 align: crate::schema::RevealAlign,
217 ) {
218 self.write(Mutation::RevealRange {
219 node_id,
220 base_version,
221 start_line: range.start.line,
222 start_column: range.start.column,
223 end_line: range.end.line,
224 end_column: range.end.column,
225 align,
226 });
227 }
228
229 /// Writes one request to fold or unfold the stretch of a code editor that starts on
230 /// `start_line`.
231 pub fn set_folded(&mut self, node_id: u32, start_line: u32, collapsed: bool) {
232 self.write(Mutation::SetFolded {
233 node_id,
234 start_line,
235 collapsed,
236 });
237 }
238
239 /// Appends the streamed tail to a Text node without resending its whole value.
240 pub fn append_text_node(&mut self, node_id: u32, text: &str) {
241 self.write(Mutation::AppendText { node_id, text });
242 }
243}
244
245/// What builds and changes the tree, seen from the boundary.
246///
247/// The Host owns everything that is not the tree and calls in here for the rest. Every
248/// method runs on the Renderer's UI thread, inside a boundary call, between a
249/// [`Batch::begin`] and a [`Batch::finish`] the Host makes on [`Runtime::batch_mut`], so a
250/// runtime writes its records and returns; it never opens or closes a batch itself.
251pub trait Runtime {
252 /// The batch this runtime writes the tree's records into. The Host writes its own
253 /// records into the same one, before and after the runtime's.
254 fn batch(&self) -> &Batch;
255
256 fn batch_mut(&mut self) -> &mut Batch;
257
258 /// Writes the whole tree. Called once on a fresh runtime, which is also what a resync
259 /// gets: the Host makes a new runtime and rebuilds it.
260 fn rebuild(&mut self);
261
262 /// Writes whatever changed since the last call: state a worker wrote, a scope that a
263 /// size class or a design system woke, the consequences of an event.
264 ///
265 /// `frame_time_nanos` is the time of the frame the Renderer is serving, when this call
266 /// is serving one, and `None` when it answers an event. A runtime whose animations and
267 /// timers run on the frame clock reads it here; one that has no clock of its own
268 /// ignores it.
269 fn render(&mut self, frame_time_nanos: Option<u64>);
270
271 /// Runs the handler an event names, and nothing else: the Host renders afterwards.
272 ///
273 /// Returns the value the boundary hands back as the call's result, which is how a key
274 /// handler says it consumed the key. An event naming a handler or a node this runtime
275 /// does not have is an error, and so is a payload that no handler can receive.
276 fn handle_event(&mut self, event: &HostEvent<'_>) -> Result<i64, ProtocolError>;
277
278 /// The application's name for a node it asked to have measured, if it asked.
279 fn size_token(&self, node_id: u32) -> Option<u32> {
280 let _ = node_id;
281 None
282 }
283
284 /// Whether the runtime has work waiting that a frame should run. A runtime that is
285 /// woken by something outside a boundary call (a timer, a task it spawned) wakes the
286 /// context's waker, and the Host turns that into a frame request.
287 fn poll_work(&mut self, context: &mut Context<'_>) -> Poll<()> {
288 let _ = context;
289 Poll::Pending
290 }
291
292 /// Runs code the application handed to the Host, a message's action for one, inside
293 /// whatever context this runtime's state writes need.
294 fn run_in_context(&mut self, action: &mut dyn FnMut()) {
295 action();
296 }
297}