From 0d9c350d5e0d0fc64d23589c256b877a93eb13a5 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 15 Dec 2022 21:16:37 -0600 Subject: [PATCH] implement for web and desktop --- packages/core/src/nodes.rs | 27 +++++++++++++++++++++++-- packages/desktop/src/controller.rs | 23 ++++++++++++++++----- packages/desktop/src/desktop_context.rs | 6 ------ packages/desktop/src/hot_reload.rs | 12 +++++------ packages/desktop/src/lib.rs | 3 +++ packages/dioxus/benches/jsframework.rs | 2 +- packages/web/src/hot_reload.rs | 12 +++++++---- packages/web/src/lib.rs | 14 ++++++++----- 8 files changed, 69 insertions(+), 30 deletions(-) diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 81ed5da06..ee0adccfa 100644 --- a/packages/core/src/nodes.rs +++ b/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>], }, diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 7139c083f..445c197b8 100644 --- a/packages/desktop/src/controller.rs +++ b/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, pub(super) proxy: EventLoopProxy, pub(super) event_tx: UnboundedSender, + #[cfg(debug_assertions)] + pub(super) templates_tx: UnboundedSender>, #[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>) -> impl Future> + '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::(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") - } } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 0c1751363..66e2d1488 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/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 diff --git a/packages/desktop/src/hot_reload.rs b/packages/desktop/src/hot_reload.rs index e2628cdd4..e1383390d 100644 --- a/packages/desktop/src/hot_reload.rs +++ b/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) -> Option>) { let latest_in_connection: Arc>>> = 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 { diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 585a7ff51..c858c0072 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -106,6 +106,9 @@ pub fn launch_with_props(root: Component

, 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; diff --git a/packages/dioxus/benches/jsframework.rs b/packages/dioxus/benches/jsframework.rs index 24ca2fe69..b3405b2bd 100644 --- a/packages/dioxus/benches/jsframework.rs +++ b/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(); diff --git a/packages/web/src/hot_reload.rs b/packages/web/src/hot_reload.rs index ac474370a..a48b5ff70 100644 --- a/packages/web/src/hot_reload.rs +++ b/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 { +pub(crate) fn init() -> UnboundedReceiver> { let (tx, rx) = futures_channel::mpsc::unbounded(); std::mem::forget(tx); @@ -16,7 +17,7 @@ pub(crate) fn init() -> UnboundedReceiver { } #[cfg(debug_assertions)] -pub(crate) fn init() -> UnboundedReceiver { +pub(crate) fn init() -> UnboundedReceiver> { use std::convert::TryInto; let window = web_sys::window().unwrap(); @@ -39,8 +40,11 @@ pub(crate) fn init() -> UnboundedReceiver { // 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::() { - if let Ok(val) = text.try_into() { - _ = tx.unbounded_send(val); + let text: Result = 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); diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 62a2bf4dd..e7acc43e9 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -202,19 +202,23 @@ pub async fn run_with_props(root: fn(Scope) -> 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 {