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: ¬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 /// 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}