Browse Source

implement for web and desktop

= 2 years ago
parent
commit
0d9c350d5e

+ 25 - 2
packages/core/src/nodes.rs

@@ -102,7 +102,7 @@ impl<'a> VNode<'a> {
 ///
 /// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
 /// ways, with the suggested approach being the unique code location (file, line, col, etc).
-#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
 pub struct Template<'a> {
     /// The name of the template. This must be unique across your entire program for template diffing to work properly
@@ -113,26 +113,47 @@ pub struct Template<'a> {
     /// The list of template nodes that make up the template
     ///
     /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
+    #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
     pub roots: &'a [TemplateNode<'a>],
 
     /// The paths of each node relative to the root of the template.
     ///
     /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
     /// topmost element, not the `roots` field.
+    #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
     pub node_paths: &'a [&'a [u8]],
 
     /// The paths of each dynamic attribute relative to the root of the template
     ///
     /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
     /// topmost element, not the `roots` field.
+    #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
     pub attr_paths: &'a [&'a [u8]],
 }
 
+#[cfg(feature = "serialize")]
+fn deserialize_leaky<'a, 'de, T: serde::Deserialize<'de>, D>(
+    deserializer: D,
+) -> Result<&'a [T], D::Error>
+where
+    T: serde::Deserialize<'de>,
+    D: serde::Deserializer<'de>,
+{
+    use serde::Deserialize;
+
+    let deserialized = Box::<[T]>::deserialize(deserializer)?;
+    Ok(&*Box::leak(deserialized))
+}
+
 /// A statically known node in a layout.
 ///
 /// This can be created at compile time, saving the VirtualDom time when diffing the tree
 #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
-#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))]
+#[cfg_attr(
+    feature = "serialize",
+    derive(serde::Serialize, serde::Deserialize),
+    serde(tag = "type")
+)]
 pub enum TemplateNode<'a> {
     /// An statically known element in the dom.
     ///
@@ -152,9 +173,11 @@ pub enum TemplateNode<'a> {
         /// A list of possibly dynamic attribues for this element
         ///
         /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
+        #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
         attrs: &'a [TemplateAttribute<'a>],
 
         /// A list of template nodes that define another set of template nodes
+        #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
         children: &'a [TemplateNode<'a>],
     },
 

+ 18 - 5
packages/desktop/src/controller.rs

@@ -1,7 +1,7 @@
 use crate::desktop_context::{DesktopContext, UserWindowEvent};
 use crate::events::{decode_event, EventMessage};
 use dioxus_core::*;
-use futures_channel::mpsc::{unbounded, UnboundedSender};
+use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
 use futures_util::StreamExt;
 #[cfg(target_os = "ios")]
 use objc::runtime::Object;
@@ -24,6 +24,8 @@ pub(super) struct DesktopController {
     pub(super) is_ready: Arc<AtomicBool>,
     pub(super) proxy: EventLoopProxy<UserWindowEvent>,
     pub(super) event_tx: UnboundedSender<serde_json::Value>,
+    #[cfg(debug_assertions)]
+    pub(super) templates_tx: UnboundedSender<Template<'static>>,
 
     #[cfg(target_os = "ios")]
     pub(super) views: Vec<*mut Object>,
@@ -39,6 +41,7 @@ impl DesktopController {
     ) -> Self {
         let edit_queue = Arc::new(Mutex::new(Vec::new()));
         let (event_tx, mut event_rx) = unbounded();
+        let (templates_tx, mut templates_rx) = unbounded();
         let proxy2 = proxy.clone();
 
         let pending_edits = edit_queue.clone();
@@ -64,6 +67,18 @@ impl DesktopController {
 
                 loop {
                     tokio::select! {
+                        template = {
+                            #[allow(unused)]
+                            fn maybe_future<'a>(templates_rx: &'a mut UnboundedReceiver<Template<'static>>) -> impl Future<Output = dioxus_core::Template<'static>> + 'a {
+                                #[cfg(debug_assertions)]
+                                return templates_rx.select_next_some();
+                                #[cfg(not(debug_assertions))]
+                                return std::future::pending();
+                            }
+                            maybe_future(&mut templates_rx)
+                        } => {
+                            dom.replace_template(template);
+                        }
                         _ = dom.wait_for_work() => {}
                         Some(json_value) = event_rx.next() => {
                             if let Ok(value) = serde_json::from_value::<EventMessage>(json_value) {
@@ -93,6 +108,8 @@ impl DesktopController {
             quit_app_on_close: true,
             proxy: proxy2,
             event_tx,
+            #[cfg(debug_assertions)]
+            templates_tx,
             #[cfg(target_os = "ios")]
             views: vec![],
         }
@@ -123,8 +140,4 @@ impl DesktopController {
             }
         }
     }
-
-    pub(crate) fn set_template(&self, _serialized_template: String) {
-        todo!("hot reloading currently WIP")
-    }
 }

+ 0 - 6
packages/desktop/src/desktop_context.rs

@@ -171,11 +171,6 @@ pub enum UserWindowEvent {
     DragWindow,
     FocusWindow,
 
-    /// Set a new Dioxus template for hot-reloading
-    ///
-    /// Is a no-op in release builds. Must fit the right format for templates
-    SetTemplate(String),
-
     Visible(bool),
     Minimize(bool),
     Maximize(bool),
@@ -223,7 +218,6 @@ impl DesktopController {
 
         match user_event {
             Initialize | EditsReady => self.try_load_ready_webviews(),
-            SetTemplate(template) => self.set_template(template),
             CloseWindow => *control_flow = ControlFlow::Exit,
             DragWindow => {
                 // if the drag_window has any errors, we don't do anything

+ 5 - 7
packages/desktop/src/hot_reload.rs

@@ -1,6 +1,6 @@
 #![allow(dead_code)]
 
-use dioxus_core::VirtualDom;
+use dioxus_core::Template;
 
 use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
 use std::io::{BufRead, BufReader};
@@ -13,7 +13,7 @@ fn handle_error(connection: std::io::Result<LocalSocketStream>) -> Option<LocalS
         .ok()
 }
 
-pub(crate) fn init(_dom: &VirtualDom) {
+pub(crate) fn init(proxy: futures_channel::mpsc::UnboundedSender<Template<'static>>) {
     let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
         Arc::new(Mutex::new(None));
 
@@ -36,11 +36,9 @@ pub(crate) fn init(_dom: &VirtualDom) {
                 let mut buf = String::new();
                 match conn.read_line(&mut buf) {
                     Ok(_) => {
-                        todo!()
-                        // let msg: SetTemplateMsg = serde_json::from_str(&buf).unwrap();
-                        // channel
-                        //     .start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
-                        //     .unwrap();
+                        let msg: Template<'static> =
+                            serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap();
+                        proxy.unbounded_send(msg).unwrap();
                     }
                     Err(err) => {
                         if err.kind() != std::io::ErrorKind::WouldBlock {

+ 3 - 0
packages/desktop/src/lib.rs

@@ -106,6 +106,9 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
     let event_loop = EventLoop::with_user_event();
     let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
 
+    #[cfg(debug_assertions)]
+    hot_reload::init(desktop.templates_tx.clone());
+
     event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
 

+ 1 - 1
packages/dioxus/benches/jsframework.rs

@@ -42,7 +42,7 @@ fn create_rows(c: &mut Criterion) {
 
     c.bench_function("create rows", |b| {
         let mut dom = VirtualDom::new(app);
-        dom.rebuild();
+        let _ = dom.rebuild();
 
         b.iter(|| {
             let g = dom.rebuild();

+ 8 - 4
packages/web/src/hot_reload.rs

@@ -2,12 +2,13 @@
 
 use futures_channel::mpsc::UnboundedReceiver;
 
+use dioxus_core::Template;
 use wasm_bindgen::closure::Closure;
 use wasm_bindgen::JsCast;
 use web_sys::{MessageEvent, WebSocket};
 
 #[cfg(not(debug_assertions))]
-pub(crate) fn init() -> UnboundedReceiver<String> {
+pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
     let (tx, rx) = futures_channel::mpsc::unbounded();
 
     std::mem::forget(tx);
@@ -16,7 +17,7 @@ pub(crate) fn init() -> UnboundedReceiver<String> {
 }
 
 #[cfg(debug_assertions)]
-pub(crate) fn init() -> UnboundedReceiver<String> {
+pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
     use std::convert::TryInto;
 
     let window = web_sys::window().unwrap();
@@ -39,8 +40,11 @@ pub(crate) fn init() -> UnboundedReceiver<String> {
     // change the rsx when new data is received
     let cl = Closure::wrap(Box::new(move |e: MessageEvent| {
         if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
-            if let Ok(val) = text.try_into() {
-                _ = tx.unbounded_send(val);
+            let text: Result<String, _> = text.try_into();
+            if let Ok(string) = text {
+                if let Ok(template) = serde_json::from_str(Box::leak(string.into_boxed_str())) {
+                    _ = tx.unbounded_send(template);
+                }
             }
         }
     }) as Box<dyn FnMut(MessageEvent)>);

+ 9 - 5
packages/web/src/lib.rs

@@ -202,19 +202,23 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
 
         // if virtualdom has nothing, wait for it to have something before requesting idle time
         // if there is work then this future resolves immediately.
-        let mut res = {
+        let (mut res, template) = {
             let work = dom.wait_for_work().fuse();
             pin_mut!(work);
 
             futures_util::select! {
-                _ = work => None,
-                _new_template = hotreload_rx.next() => {
-                    todo!("Implement hot reload");
+                _ = work => (None, None),
+                new_template = hotreload_rx.next() => {
+                    (None, new_template)
                 }
-                evt = rx.next() => evt
+                evt = rx.next() => (evt, None)
             }
         };
 
+        if let Some(template) = template {
+            dom.replace_template(template);
+        }
+
         // Dequeue all of the events from the channel in send order
         // todo: we should re-order these if possible
         while let Some(evt) = res {