Skip to main content

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        if let Some(overlay) = theme.overlay {
90            self.write(Mutation::SetOverlayTokens(*overlay));
91        }
92    }
93
94    /// Sets application-level overlay theme tokens for Dialog, Menu, Dropdown and Sheet.
95    pub fn set_overlay_tokens(&mut self, tokens: crate::schema::OverlayTokens) {
96        self.write(Mutation::SetOverlayTokens(tokens));
97    }
98
99    /// The root window record. Written once per rebuild, never per frame.
100    pub fn set_window(&mut self, window: Window) {
101        self.write(Mutation::SetWindow(window));
102    }
103
104    pub fn set_text_node(&mut self, node_id: u32, text: &str, selection: Option<Selection>) {
105        self.write(Mutation::SetText {
106            node_id,
107            text,
108            selection,
109        });
110    }
111
112    /// Copies one asset into the batch. The bytes ride behind the records, and the
113    /// Renderer takes its own copy inside the call that carries them.
114    pub fn register_asset(&mut self, asset_id: u32, kind: AssetKind, bytes: &[u8]) {
115        self.write(Mutation::RegisterAsset {
116            asset_id,
117            kind,
118            bytes,
119        });
120    }
121
122    pub fn release_asset(&mut self, asset_id: u32) {
123        self.write(Mutation::ReleaseAsset { asset_id });
124    }
125
126    /// Writes one transient message into the batch.
127    ///
128    /// It names no node, because a message is not in the tree: it is a sentence with a
129    /// lifetime, and that lifetime belongs to the Renderer.
130    pub fn show_message(
131        &mut self,
132        handler_id: u64,
133        text: &str,
134        action: &str,
135        duration: MessageDuration,
136    ) {
137        self.write(Mutation::ShowMessage {
138            handler_id,
139            text,
140            action,
141            duration,
142        });
143    }
144
145    /// Writes one notification command into the batch.
146    ///
147    /// Like a message it names no node: what it asks for happens outside the window, and
148    /// the Renderer is the side that owns the platform it happens on.
149    pub(crate) fn notification(&mut self, command: &crate::notification::NotificationCommand) {
150        use crate::notification::NotificationCommand;
151        match command {
152            NotificationCommand::Post(notification) => {
153                self.write(Mutation::PostNotification {
154                    key: &notification.key,
155                    title: &notification.title,
156                    body: &notification.body,
157                    channel: &notification.channel,
158                    action_1: &notification.actions[0],
159                    action_2: &notification.actions[1],
160                    importance: notification.importance,
161                    presentation: notification.presentation,
162                });
163            }
164            NotificationCommand::Withdraw(key) => {
165                self.write(Mutation::WithdrawNotification { key });
166            }
167            NotificationCommand::RequestPermission => {
168                self.write(Mutation::RequestNotificationPermission);
169            }
170        }
171    }
172
173    /// Writes one clipboard write. It names no node: the clipboard is the platform's.
174    pub fn set_clipboard_text(&mut self, text: &str) {
175        self.write(Mutation::SetClipboardText { text });
176    }
177
178    /// Writes one clipboard read request. It names no node either.
179    pub fn read_clipboard(&mut self, request: u32) {
180        self.write(Mutation::ReadClipboard { request });
181    }
182
183    /// Writes one operation on a file the Renderer holds a handle for.
184    pub(crate) fn file_call(&mut self, call: &crate::picked_file::Call) {
185        self.write(Mutation::FileCall {
186            request: call.request,
187            op: call.op,
188            create: call.create,
189            handle: call.handle,
190            name: &call.name,
191            data: &call.data,
192        });
193    }
194
195    /// Writes one storage operation for the Renderer to carry out. It names no node either.
196    pub(crate) fn storage_call(&mut self, call: &crate::storage::RendererCall) {
197        self.write(Mutation::StorageCall {
198            request: call.request,
199            op: call.op,
200            store: &call.store,
201            key: &call.key,
202            data: &call.data,
203        });
204    }
205
206    /// Lets the Renderer go of a file handle nothing on this side names any more.
207    pub(crate) fn release_file_handle(&mut self, handle: u32) {
208        self.write(Mutation::ReleaseFileHandle { handle });
209    }
210
211    /// Writes one request to scroll the container `node_id` to an offset in physical
212    /// pixels. An axis left `None` is not touched; a request that names neither axis is
213    /// refused when the batch is finished.
214    ///
215    /// A request, not a command: the Renderer owns the position. It keeps the offset
216    /// inside what the content allows, ignores the request while the user is dragging or
217    /// flinging, and says where the container ended up in `ScrollChanged`.
218    pub fn scroll_to(&mut self, node_id: u32, request: crate::schema::ScrollTo) {
219        use crate::schema::scroll_to_flags as flag;
220        let mut flags = 0;
221        if request.animate {
222            flags |= flag::ANIMATE;
223        }
224        if request.x_px.is_some() {
225            flags |= flag::X;
226        }
227        if request.y_px.is_some() {
228            flags |= flag::Y;
229        }
230        let x_px = request.x_px.unwrap_or(0.0);
231        let y_px = request.y_px.unwrap_or(0.0);
232        if flags & (flag::X | flag::Y) == 0 || !x_px.is_finite() || !y_px.is_finite() {
233            // The Renderer would refuse it with a protocol error after the batch had
234            // crossed. Refusing it here names the mistake where it was made.
235            self.fail(ProtocolError::InvalidValueKind(0));
236            return;
237        }
238        self.write(Mutation::ScrollTo {
239            node_id,
240            item: request.item,
241            flags,
242            x_px,
243            y_px,
244        });
245    }
246
247    /// Writes one request to bring a rectangle into view in the container `node_id`. One
248    /// request moves one container: to show something inside nested containers, write one
249    /// for each of them, innermost first.
250    pub fn scroll_into_view(&mut self, node_id: u32, request: crate::schema::ScrollIntoView) {
251        use crate::schema::{ScrollTarget, scroll_into_view_flags, scroll_target_kind};
252        let (target_kind, target, rect) = match request.target {
253            ScrollTarget::Content(rect) => (scroll_target_kind::CONTENT, 0, rect),
254            ScrollTarget::Node(node, rect) => (scroll_target_kind::NODE, node, rect),
255            ScrollTarget::Item(item, rect) => (scroll_target_kind::ITEM, item, rect),
256        };
257        let numbers = [rect.x_px, rect.y_px, rect.width_px, rect.height_px];
258        if numbers.iter().any(|value| !value.is_finite())
259            || rect.width_px < 0.0
260            || rect.height_px < 0.0
261        {
262            self.fail(ProtocolError::InvalidValueKind(0));
263            return;
264        }
265        self.write(Mutation::ScrollIntoView {
266            node_id,
267            target,
268            target_kind,
269            align_x: request.align_x as u16,
270            align_y: request.align_y as u16,
271            flags: if request.animate {
272                scroll_into_view_flags::ANIMATE
273            } else {
274                0
275            },
276            rect,
277        });
278    }
279
280    /// Writes one file dialog request. Like a notification it names no node: the dialog is
281    /// not in the tree, and `window` only says which window it is modal for.
282    pub(crate) fn file_dialog(&mut self, request: &crate::file_dialog::Request) {
283        self.write(Mutation::ShowFileDialog {
284            request: request.id,
285            window: request.window,
286            kind: request.kind,
287            default_filter: request.default_filter,
288            title: &request.title,
289            directory: &request.directory,
290            name: &request.name,
291            filters: &request.filters,
292        });
293    }
294
295    /// Writes one edit the application asked of a code editor: replace `range` of the
296    /// document as it stood at `base_version` with `text`. The Renderer moves it past what
297    /// the reader typed since, or refuses it and reports `request_id` back.
298    pub fn edit_code(
299        &mut self,
300        node_id: u32,
301        request_id: u32,
302        base_version: u32,
303        range: crate::code::CodeRange,
304        text: &str,
305    ) {
306        self.write(Mutation::EditCode {
307            node_id,
308            request_id,
309            base_version,
310            start_line: range.start.line,
311            start_column: range.start.column,
312            end_line: range.end.line,
313            end_column: range.end.column,
314            text,
315        });
316    }
317
318    /// Writes one request to put a code editor's selection between two places in the
319    /// document as it stood at `base_version`. The Renderer moves both ends past what the
320    /// reader typed since and drops the request, with a protocol error, where an end is not
321    /// a place in the document.
322    pub fn set_selection(
323        &mut self,
324        node_id: u32,
325        base_version: u32,
326        anchor: crate::code::Position,
327        active: crate::code::Position,
328    ) {
329        self.write(Mutation::SetSelection {
330            node_id,
331            base_version,
332            anchor_line: anchor.line,
333            anchor_column: anchor.column,
334            active_line: active.line,
335            active_column: active.column,
336        });
337    }
338
339    /// Writes one request to scroll a code editor until `range`, as the document stood at
340    /// `base_version`, is on screen. A range inside a folded stretch unfolds it.
341    pub fn reveal_range(
342        &mut self,
343        node_id: u32,
344        base_version: u32,
345        range: crate::code::CodeRange,
346        align: crate::schema::RevealAlign,
347    ) {
348        self.write(Mutation::RevealRange {
349            node_id,
350            base_version,
351            start_line: range.start.line,
352            start_column: range.start.column,
353            end_line: range.end.line,
354            end_column: range.end.column,
355            align,
356        });
357    }
358
359    /// Writes one request to fold or unfold the stretch of a code editor that starts on
360    /// `start_line`.
361    pub fn set_folded(&mut self, node_id: u32, start_line: u32, collapsed: bool) {
362        self.write(Mutation::SetFolded {
363            node_id,
364            start_line,
365            collapsed,
366        });
367    }
368
369    /// Writes one request to scroll a code editor so that `offset_px` physical pixels of its
370    /// document are above the top of its view. The offset is the unit `CodeScrolled`
371    /// reports, so what one editor reported can be given to another to keep the two level.
372    /// The Renderer keeps it inside what the document allows.
373    pub fn set_scroll_offset(&mut self, node_id: u32, offset_px: f64) {
374        self.write(Mutation::SetScrollOffset { node_id, offset_px });
375    }
376
377    /// Appends the streamed tail to a Text node without resending its whole value.
378    pub fn append_text_node(&mut self, node_id: u32, text: &str) {
379        self.write(Mutation::AppendText { node_id, text });
380    }
381
382    /// Requests input focus for `node_id`, or clears focus when `node_id` is 0.
383    pub fn request_focus(&mut self, node_id: u32) {
384        self.write(Mutation::RequestFocus { node_id });
385    }
386}
387
388/// What builds and changes the tree, seen from the boundary.
389///
390/// The Host owns everything that is not the tree and calls in here for the rest. Every
391/// method runs on the Renderer's UI thread, inside a boundary call, between a
392/// [`Batch::begin`] and a [`Batch::finish`] the Host makes on [`Runtime::batch_mut`], so a
393/// runtime writes its records and returns; it never opens or closes a batch itself.
394pub trait Runtime {
395    /// The batch this runtime writes the tree's records into. The Host writes its own
396    /// records into the same one, before and after the runtime's.
397    fn batch(&self) -> &Batch;
398
399    fn batch_mut(&mut self) -> &mut Batch;
400
401    /// Writes the whole tree. Called once on a fresh runtime, which is also what a resync
402    /// gets: the Host makes a new runtime and rebuilds it.
403    fn rebuild(&mut self);
404
405    /// Writes whatever changed since the last call: state a worker wrote, a scope that a
406    /// size class or a design system woke, the consequences of an event.
407    ///
408    /// `frame_time_nanos` is the time of the frame the Renderer is serving, when this call
409    /// is serving one, and `None` when it answers an event. A runtime whose animations and
410    /// timers run on the frame clock reads it here; one that has no clock of its own
411    /// ignores it.
412    fn render(&mut self, frame_time_nanos: Option<u64>);
413
414    /// Runs the handler an event names, and nothing else: the Host renders afterwards.
415    ///
416    /// Returns the value the boundary hands back as the call's result, which is how a key
417    /// handler says it consumed the key. An event naming a handler or a node this runtime
418    /// does not have is an error, and so is a payload that no handler can receive.
419    fn handle_event(&mut self, event: &HostEvent<'_>) -> Result<i64, ProtocolError>;
420
421    /// The application's name for a node it asked to have measured, if it asked.
422    fn size_token(&self, node_id: u32) -> Option<u32> {
423        let _ = node_id;
424        None
425    }
426
427    /// Whether `node_id` is a `Window` node. A window's size and zoom reports name
428    /// it, and nothing else, so a runtime without windows has none.
429    fn is_window(&self, node_id: u32) -> bool {
430        let _ = node_id;
431        false
432    }
433
434    /// Whether the runtime has work waiting that a frame should run. A runtime that is
435    /// woken by something outside a boundary call (a timer, a task it spawned) wakes the
436    /// context's waker, and the Host turns that into a frame request.
437    fn poll_work(&mut self, context: &mut Context<'_>) -> Poll<()> {
438        let _ = context;
439        Poll::Pending
440    }
441
442    /// Runs code the application handed to the Host, a message's action for one, inside
443    /// whatever context this runtime's state writes need.
444    fn run_in_context(&mut self, action: &mut dyn FnMut()) {
445        action();
446    }
447}