nuit_core/ffi/
root.rs

1use std::{ffi::{c_void, c_char, CString, CStr}, str};
2
3use crate::{Root, View};
4
5/// A C/FFI-compatible wrapper around `Root<T>`.
6#[repr(C)]
7pub struct CRoot {
8    /// The opaque pointer to the owned underlying Rust `NuitRoot<T>`.
9    wrapped: *mut c_void,
10    /// Renders the view to an owned JSON-serialized node tree.
11    /// **Callers are responsible for calling `nuit_drop_string` on this string!**
12    render_json: extern "C" fn(*const CRoot) -> *const c_char,
13    /// Fires an to the given JSON-serialized id path with the given JSON-serialized event.
14    fire_event_json: extern "C" fn(*const CRoot, *const c_char, *const c_char),
15    /// Registers a callback that we (the Rust side) can use to trigger UI updates.
16    set_update_callback: extern "C" fn(*const CRoot, extern "C" fn(*const c_char)),
17}
18
19extern "C" fn render_json_impl<T>(c_root: *const CRoot) -> *const c_char where T: View {
20    unsafe {
21        let root = (*c_root).wrapped as *mut Root<T>;
22        let json = (*root).render_json();
23        let c_string = CString::new(json).expect("Could not convert JSON to C string");
24        c_string.into_raw()
25    }
26}
27
28extern "C" fn fire_event_json_impl<T>(c_root: *const CRoot, raw_id_path_json: *const c_char, raw_event_json: *const c_char) where T: View {
29    unsafe {
30        let root = (*c_root).wrapped as *mut Root<T>;
31        let id_path_json = str::from_utf8(CStr::from_ptr(raw_id_path_json).to_bytes()).expect("Could not decode id path JSON");
32        let event_json = str::from_utf8(CStr::from_ptr(raw_event_json).to_bytes()).expect("Could not decode event JSON");
33        (*root).fire_event_json(id_path_json, event_json)
34    }
35}
36
37extern "C" fn set_update_callback_impl<T>(c_root: *const CRoot, update_callback: extern "C" fn(*const c_char)) where T: View {
38    unsafe {
39        let root = (*c_root).wrapped as *mut Root<T>;
40        (*root).set_update_callback(move |update| {
41            let update_json = serde_json::to_string(update).expect("Could not encode update to JSON");
42            let update_json_c_string = CString::new(update_json).expect("Could not convert update JSON to C string");
43            update_callback(update_json_c_string.as_ptr())
44        });
45    }
46}
47
48impl<T> From<Box<Root<T>>> for CRoot where T: View {
49    fn from(value: Box<Root<T>>) -> Self {
50        Self {
51            wrapped: Box::into_raw(value) as *mut c_void,
52            render_json: render_json_impl::<T>,
53            fire_event_json: fire_event_json_impl::<T>,
54            set_update_callback: set_update_callback_impl::<T>,
55        }
56    }
57}
58
59impl Drop for CRoot {
60    fn drop(&mut self) {
61        unsafe {
62            drop(Box::from_raw(self.wrapped as *mut c_void));
63        }
64    }
65}