Skip to main content

tilo_app/framework/
dev_reload.rs

1//! The symbols a `thin` app library exports so `tilo-app dev` can run new
2//! code inside the windows the process already has.
3
4use tilo_resource::{Doc, Reader, Resource};
5use ui_core::permissions::{LivePermissions, Role};
6use ui_core::widget::AppWidget;
7
8use super::app::{Editing, ResourceApp, action_registry, build_root, role_permissions};
9use super::scenario::Run;
10
11/// Address of the `ui` this library linked. The host compares it with its own
12/// and refuses the library when they differ.
13pub fn linked_ui() -> *const () {
14    ui_core::app::dev_stamp_addr()
15}
16
17/// Build `A`'s current view and hand it, with the decoders of the edits it
18/// makes, to the host's `ui`.
19///
20/// Returns `1` when the view was published. The document, route and caret
21/// already in the open windows are restored by the host after it installs
22/// this view. The decoders must come from this library too: the view encodes
23/// its edits with this code, which the running code may not read.
24pub fn publish_root<A: ResourceApp>() -> i32 {
25    let mut doc = Doc::new();
26    if <A::Resource as Resource>::initialize(&mut doc).is_err() {
27        return 0;
28    }
29    let Ok(reader) = Reader::<A::Resource>::read(&doc) else {
30        return 0;
31    };
32    let permissions = LivePermissions::fixed(role_permissions("dev", Role::Owner).borrow().clone());
33    let me = ui_core::collaboration::local_user_id().0;
34    let root = build_root::<A>(
35        reader,
36        Editing::Live {
37            permissions,
38            me,
39            run: Run::live(None),
40        },
41        ui_core::storage::AppStorage::for_app(A::ID),
42        super::settings::Hosted::default(),
43    );
44    let view: Box<dyn AppWidget> = root.into_widget();
45    ui_core::app::publish_dev_root(ui_core::app::DevRoot {
46        view,
47        registry: action_registry::<A>(),
48    });
49    1
50}
51
52/// Copy one window's state back to the host.
53///
54/// # Safety
55/// `state` points at `state_len` readable bytes. `out` points at `out_cap`
56/// writable bytes. Both live for this call.
57pub unsafe fn copy_window_state(
58    state: *const u8,
59    state_len: usize,
60    out: *mut u8,
61    out_cap: usize,
62) -> isize {
63    if state_len > out_cap || (state_len > 0 && (state.is_null() || out.is_null())) {
64        return -1;
65    }
66    if state_len > 0 {
67        // SAFETY: the caller upholds the pointer contract above.
68        unsafe { std::ptr::copy_nonoverlapping(state, out, state_len) };
69    }
70    isize::try_from(state_len).unwrap_or(-1)
71}