Skip to main content

compose_rust/runtime/
host.rs

1//! The slot table runtime as the boundary sees it, in two halves.
2//!
3//! [`Recomposer`] is the part that builds and changes the tree: it composes the root,
4//! recomposes what a state change invalidated, and runs the handler an event names. It
5//! writes the tree's records and nothing else, so it can sit behind any Host that owns
6//! the rest of a batch. That is the same division the Dioxus adapter has, where a
7//! `VirtualDom` builds the tree and the Host owns the theme, the window, assets, messages,
8//! notifications and the frame clock.
9//!
10//! That rest is compose-rust's own [`Host`], which drives any [`Runtime`]; the Recomposer
11//! is one. The window, the design system and the notification permission reach the
12//! composition the way they reach a Dioxus hook, by subscribing to the modules the Host
13//! publishes them to. [`ComposeHost`] is the Host with a Recomposer in it.
14
15use super::Composition;
16use super::composer::{Composer, EventCallback, ROOT};
17use super::seam::{Batch, Runtime};
18use super::{drain_graveyard, effects, state, with_composer};
19use crate::boundary::{Host, request_frame_from_worker};
20use crate::protocol::{HostEvent, ProtocolError};
21use crate::schema::{EventPayload, Theme};
22use crate::{KeyEvent, RangeRequest};
23use std::any::Any;
24use std::rc::Rc;
25use std::sync::Arc;
26
27/// The slot table runtime for one root: what composes the tree and runs its handlers.
28///
29/// Every method enters the composition for its own length, so it may be called from any
30/// Host call on the UI thread. The tree's records go into [`Recomposer::writer`]'s batch,
31/// between whatever the Host writes before and after them.
32pub struct Recomposer {
33    content: Rc<dyn Fn()>,
34    composition: Rc<Composition>,
35    /// What the Renderer reports, followed the way a Dioxus hook follows it, for as long
36    /// as this composition lives.
37    _subscriptions: Vec<Box<dyn Any>>,
38}
39
40impl Recomposer {
41    /// A runtime for `content`, composed by [`Recomposer::rebuild`].
42    pub fn new(content: impl Fn() + 'static) -> Self {
43        let composition = Composition::new();
44        let (window, design, permission) = {
45            let composer = composition.composer.borrow();
46            (
47                composer.ambient.window.clone(),
48                composer.ambient.design.clone(),
49                composer.ambient.permission.clone(),
50            )
51        };
52        // The values themselves stay in the modules the Host publishes them to. A state
53        // per value is what lets a composable read one and be run again when it moves,
54        // and the subscription is what moves it.
55        let subscriptions: Vec<Box<dyn Any>> = vec![
56            Box::new(crate::window::subscribe(Arc::new(move || {
57                window.set(crate::window::window_size())
58            }))),
59            Box::new(crate::design::subscribe(Arc::new(move || {
60                design.set(crate::design::design_system())
61            }))),
62            Box::new(crate::notification::subscribe_permission(Arc::new(
63                move || permission.set(crate::notification::notification_permission()),
64            ))),
65        ];
66        Self {
67            content: Rc::new(content),
68            composition,
69            _subscriptions: subscriptions,
70        }
71    }
72
73    /// The batch arena.
74    pub fn arena(&self) -> (*const u8, usize) {
75        self.composition.composer.borrow().writer.arena()
76    }
77
78    /// The application's name for a node it asked to have measured, if it asked.
79    pub fn size_token(&self, node_id: u32) -> Option<u32> {
80        self.composition
81            .composer
82            .borrow()
83            .writer
84            .size_token(node_id)
85    }
86
87    /// Composes the whole tree. Called once, on a fresh runtime.
88    pub fn rebuild(&mut self) {
89        let _composition = self.composition.enter();
90        with_composer(|composer| {
91            composer.abandon_composition();
92            composer.begin_root();
93        });
94        (self.content)();
95        with_composer(Composer::end_root);
96        self.after_composition();
97    }
98
99    /// Writes whatever changed since the last call: a frame's time to whatever waits for
100    /// one, the futures that woke, and the scopes the states written since invalidated.
101    pub fn render(&mut self, frame_time_nanos: Option<u64>) {
102        let _composition = self.composition.enter();
103        if let Some(now) = frame_time_nanos {
104            with_composer(|composer| composer.executor.deliver_frame(now));
105        }
106        effects::poll_woken();
107        self.recompose();
108    }
109
110    /// Runs the handler an event names, and nothing else; the caller renders afterwards.
111    ///
112    /// Returns what the boundary hands back as the call's result, which is how a key
113    /// handler says it consumed the key. An event naming a handler or node this
114    /// composition does not have is an error, and so is a payload no handler can take.
115    pub fn handle_event(&mut self, event: &HostEvent<'_>) -> Result<i64, ProtocolError> {
116        let _composition = self.composition.enter();
117        let Some((node, callback)) = with_composer(|composer| {
118            composer
119                .handlers
120                .get(&event.handler_id)
121                .map(|entry| (entry.node, entry.callback.clone()))
122        })
123        .flatten() else {
124            return Err(ProtocolError::InvalidValueKind(0));
125        };
126        if node != event.node_id {
127            return Err(ProtocolError::InvalidValueKind(0));
128        }
129        deliver(&callback, &event.payload)
130    }
131
132    /// Runs code the application handed to the Host, a message's action for one, with
133    /// this composition current, so the states it writes are picked up by the next
134    /// render.
135    pub fn run_in_context(&mut self, action: &mut dyn FnMut()) {
136        let _composition = self.composition.enter();
137        action();
138    }
139
140    /// One round of recomposition: the states written since the last round mark their
141    /// readers, and the readers run, outermost first.
142    ///
143    /// A state written while this round composes is left for the next frame, which the
144    /// write asks for: composing describes the screen, and a description that changes
145    /// what it describes is a loop the frame clock should pace, not this call.
146    fn recompose(&mut self) {
147        with_composer(Composer::abandon_composition);
148        state::apply_state_changes();
149        let root = with_composer(|composer| composer.root_invalid()).unwrap_or(false);
150        if root {
151            with_composer(Composer::begin_root);
152            (self.content)();
153            with_composer(Composer::end_root);
154        }
155        let invalid = with_composer(Composer::take_invalid).unwrap_or_default();
156        for group in invalid {
157            if group == ROOT {
158                continue;
159            }
160            let restart = with_composer(|composer| composer.prepare_restart(group)).flatten();
161            if let Some(restart) = restart {
162                restart();
163                with_composer(Composer::finish_restart);
164            }
165        }
166        self.after_composition();
167    }
168
169    fn after_composition(&mut self) {
170        drain_graveyard();
171        effects::apply_effects();
172        drain_graveyard();
173        effects::poll_woken();
174        // Whatever was written during this round, by the composition, an effect or a
175        // future, is the next frame's.
176        let pending = with_composer(|composer| {
177            !composer
178                .signal
179                .dirty
180                .lock()
181                .unwrap_or_else(|poison| poison.into_inner())
182                .is_empty()
183                || !composer.invalid.is_empty()
184        })
185        .unwrap_or(false);
186        if pending {
187            request_frame_from_worker();
188        }
189    }
190}
191
192/// The Host of a composable application: compose-rust's [`Host`] driving a
193/// [`Recomposer`].
194///
195/// The boundary builds one on the UI thread for an application launched with
196/// [`launch`]. Tests and benchmarks build one directly, which is why it is public; it
197/// derefs to the [`Host`], whose calls are the boundary's.
198pub struct ComposeHost(Host);
199
200impl ComposeHost {
201    /// The Host for `content`, in the theme the application launched with.
202    pub fn new(content: fn()) -> Self {
203        Self(Host::new(recomposer_for(content)))
204    }
205
206    /// The same, in the given theme.
207    pub fn with_theme(content: fn(), theme: Theme) -> Self {
208        Self(Host::with_theme(recomposer_for(content), theme))
209    }
210
211    /// A Host for a closure rather than a function, for tests that compose something
212    /// built at run time. The closure is shared with the factory a resync calls, so it
213    /// has to be `Send` and `Sync` like any other root.
214    pub fn with_content(content: impl Fn() + Send + Sync + 'static) -> Self {
215        let content = Arc::new(content);
216        Self(Host::new(move || {
217            let content = Arc::clone(&content);
218            Box::new(Recomposer::new(move || content())) as Box<dyn Runtime>
219        }))
220    }
221}
222
223impl std::ops::Deref for ComposeHost {
224    type Target = Host;
225
226    fn deref(&self) -> &Host {
227        &self.0
228    }
229}
230
231impl std::ops::DerefMut for ComposeHost {
232    fn deref_mut(&mut self) -> &mut Host {
233        &mut self.0
234    }
235}
236
237/// The runtime factory for one composable root: what [`crate::LaunchBuilder::launch_runtime`]
238/// and the browser's start take.
239pub fn recomposer_for(content: fn()) -> impl Fn() -> Box<dyn Runtime> + Send + Sync + 'static {
240    move || Box::new(Recomposer::new(content)) as Box<dyn Runtime>
241}
242
243impl crate::LaunchBuilder {
244    /// Runs a composable as the application. Does not return while it is running, and
245    /// ends the process with a failing status if the renderer loop could not run at all.
246    ///
247    /// ```ignore
248    /// #[composable]
249    /// fn app() {
250    ///     Text("Hello");
251    /// }
252    ///
253    /// fn main() {
254    ///     compose_rust::LaunchBuilder::new().launch(app);
255    /// }
256    /// ```
257    pub fn launch(self, content: fn()) {
258        self.launch_runtime(recomposer_for(content));
259    }
260
261    /// [`crate::LaunchBuilder::launch`] without the exit: the status the renderer loop
262    /// ended with.
263    pub fn try_launch(self, content: fn()) -> i32 {
264        self.try_launch_runtime(recomposer_for(content))
265    }
266
267    /// The same as `launch`, under Compose Desktop's name for it.
268    pub fn application(self, content: fn()) {
269        self.launch(content);
270    }
271}
272
273/// Runs a composable as the application, with every launch choice left to its default.
274///
275/// ```ignore
276/// #[composable]
277/// fn counter() {
278///     let count = remember(|| mutable_state_of(0));
279///     let more = count.clone();
280///     Button(format!("{}", count.get())).on_click(move || more.update(|count| *count += 1));
281/// }
282///
283/// fn main() {
284///     compose_rust::launch(counter);
285/// }
286/// ```
287pub fn launch(content: fn()) {
288    crate::LaunchBuilder::new().launch(content);
289}
290
291/// The same as [`launch`], under Compose Desktop's name for it.
292pub fn application(content: fn()) {
293    launch(content);
294}
295
296/// Calls a handler with an event's payload, converted to what the handler takes.
297fn deliver(callback: &EventCallback, payload: &EventPayload<'_>) -> Result<i64, ProtocolError> {
298    match (callback, payload) {
299        (
300            EventCallback::Unit(handler),
301            EventPayload::Clicked | EventPayload::FocusLost | EventPayload::FilesEntered,
302        ) => (*handler.borrow_mut())(),
303        (EventCallback::Text(handler), EventPayload::TextChanged(text))
304        | (EventCallback::Text(handler), EventPayload::TextSubmitted(text)) => {
305            (*handler.borrow_mut())((*text).to_owned())
306        }
307        (
308            EventCallback::Key(handler),
309            EventPayload::KeyDown {
310                key,
311                shift_key,
312                ctrl_key,
313                alt_key,
314                meta_key,
315            },
316        ) => {
317            let event = KeyEvent::new(*key, *shift_key, *ctrl_key, *alt_key, *meta_key);
318            (*handler.borrow_mut())(event.clone());
319            return Ok(i64::from(event.consumed()));
320        }
321        (
322            EventCallback::Pointer(handler),
323            EventPayload::Pointer {
324                kind,
325                pointer_id,
326                pointer_type,
327                x,
328                y,
329                buttons,
330                button,
331                modifiers,
332                click_count,
333                delta_x,
334                delta_y,
335                delta_mode,
336                timestamp_nanos,
337            },
338        ) => {
339            let event = crate::PointerEvent::new(
340                *kind,
341                *pointer_id,
342                *pointer_type,
343                *x,
344                *y,
345                *buttons,
346                *button,
347                *modifiers,
348                *click_count,
349                *delta_x,
350                *delta_y,
351                *delta_mode,
352                *timestamp_nanos,
353            );
354            (*handler.borrow_mut())(event.clone());
355            return Ok(i64::from(event.consumed()));
356        }
357        (EventCallback::Value(handler), EventPayload::ValueChanged(value)) => {
358            (*handler.borrow_mut())(*value)
359        }
360        (EventCallback::Range(handler), EventPayload::RangeRequested { start, count }) => {
361            (*handler.borrow_mut())(RangeRequest::new(*start, *count))
362        }
363        (EventCallback::Files(handler), EventPayload::FilesDropped(paths)) => {
364            (*handler.borrow_mut())(crate::FileDrop::new(paths))
365        }
366        _ => return Err(ProtocolError::InvalidValueKind(0)),
367    }
368    Ok(0)
369}
370
371impl Composer {
372    /// Drops any walk a panic left half done, so the next call starts from a clean stack.
373    pub(crate) fn abandon_composition(&mut self) {
374        self.clear_walk();
375    }
376}
377
378impl Runtime for Recomposer {
379    fn batch(&self) -> &Batch {
380        let composer = self.composition.composer.as_ptr();
381        // SAFETY: the composer is only borrowed inside this runtime's own calls, which take
382        // `&mut self`, so no borrow is alive while the Host holds this reference, and the
383        // reference borrows `self`, so no runtime call can start while it lives.
384        unsafe { (*composer).writer.batch() }
385    }
386
387    fn batch_mut(&mut self) -> &mut Batch {
388        let composer = self.composition.composer.as_ptr();
389        // SAFETY: as for `batch`, with `&mut self` making this the only reference.
390        unsafe { (*composer).writer.batch_mut() }
391    }
392
393    fn rebuild(&mut self) {
394        Recomposer::rebuild(self);
395    }
396
397    fn render(&mut self, frame_time_nanos: Option<u64>) {
398        Recomposer::render(self, frame_time_nanos);
399    }
400
401    fn handle_event(&mut self, event: &HostEvent<'_>) -> Result<i64, ProtocolError> {
402        Recomposer::handle_event(self, event)
403    }
404
405    fn size_token(&self, node_id: u32) -> Option<u32> {
406        Recomposer::size_token(self, node_id)
407    }
408
409    fn run_in_context(&mut self, action: &mut dyn FnMut()) {
410        Recomposer::run_in_context(self, action);
411    }
412}