From 107357489605ab9d524847fac3fba67a04dd8799 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 11 Jan 2023 13:40:02 -0600 Subject: [PATCH 01/44] implement hot reloading for desktop --- Cargo.toml | 3 + packages/desktop/Cargo.toml | 2 +- packages/desktop/src/desktop_context.rs | 3 + packages/desktop/src/hot_reload.rs | 46 +++++--------- packages/desktop/src/lib.rs | 12 ++++ packages/hot-reload-macro/Cargo.toml | 13 ++++ packages/hot-reload-macro/src/lib.rs | 7 +++ packages/hot-reload/Cargo.toml | 17 +++++ packages/hot-reload/src/lib.rs | 84 +++++++++++++++++++++++++ 9 files changed, 155 insertions(+), 32 deletions(-) create mode 100644 packages/hot-reload-macro/Cargo.toml create mode 100644 packages/hot-reload-macro/src/lib.rs create mode 100644 packages/hot-reload/Cargo.toml create mode 100644 packages/hot-reload/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 7d8cc6871..ebfe288c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,8 @@ members = [ "packages/native-core-macro", "packages/rsx-rosetta", "packages/signals", + "packages/hot-reload", + "packages/hot-reload-macro", "docs/guide", ] @@ -43,6 +45,7 @@ publish = false dioxus = { path = "./packages/dioxus" } dioxus-desktop = { path = "./packages/desktop", features = ["transparent"] } dioxus-ssr = { path = "./packages/ssr" } +dioxus-hot-reload = { path = "./packages/hot-reload" } dioxus-router = { path = "./packages/router" } dioxus-signals = { path = "./packages/signals" } fermi = { path = "./packages/fermi" } diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 5f1ea27d7..16cc622c6 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -33,7 +33,7 @@ webbrowser = "0.8.0" infer = "0.11.0" dunce = "1.0.2" -interprocess = { version = "1.1.1", optional = true } +interprocess = { version = "1.2.1", optional = true } futures-util = "0.3.25" [target.'cfg(target_os = "ios")'.dependencies] diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index d3b9216b0..720223e6c 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -8,6 +8,7 @@ use crate::events::IpcMessage; use crate::Config; use crate::WebviewHandler; use dioxus_core::ScopeState; +use dioxus_core::Template; use dioxus_core::VirtualDom; use serde_json::Value; use wry::application::event_loop::EventLoopProxy; @@ -262,6 +263,8 @@ pub enum EventData { Ipc(IpcMessage), + TemplateUpdated(Template<'static>), + NewWindow, CloseWindow, diff --git a/packages/desktop/src/hot_reload.rs b/packages/desktop/src/hot_reload.rs index e1383390d..20907f59d 100644 --- a/packages/desktop/src/hot_reload.rs +++ b/packages/desktop/src/hot_reload.rs @@ -2,43 +2,29 @@ use dioxus_core::Template; -use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; +use interprocess::local_socket::LocalSocketStream; use std::io::{BufRead, BufReader}; -use std::time::Duration; -use std::{sync::Arc, sync::Mutex}; +use wry::application::{event_loop::EventLoopProxy, window::WindowId}; -fn handle_error(connection: std::io::Result) -> Option { - connection - .map_err(|error| eprintln!("Incoming connection failed: {}", error)) - .ok() -} +use crate::desktop_context::{EventData, UserWindowEvent}; -pub(crate) fn init(proxy: futures_channel::mpsc::UnboundedSender>) { - let latest_in_connection: Arc>>> = - Arc::new(Mutex::new(None)); - - let latest_in_connection_handle = latest_in_connection.clone(); - - // connect to processes for incoming data +pub(crate) fn init(proxy: EventLoopProxy) { std::thread::spawn(move || { let temp_file = std::env::temp_dir().join("@dioxusin"); - - if let Ok(listener) = LocalSocketListener::bind(temp_file) { - for conn in listener.incoming().filter_map(handle_error) { - *latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn)); - } - } - }); - - std::thread::spawn(move || { - loop { - if let Some(conn) = &mut *latest_in_connection.lock().unwrap() { + if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) { + let mut buf_reader = BufReader::new(socket); + loop { let mut buf = String::new(); - match conn.read_line(&mut buf) { + match buf_reader.read_line(&mut buf) { Ok(_) => { - let msg: Template<'static> = + let template: Template<'static> = serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap(); - proxy.unbounded_send(msg).unwrap(); + proxy + .send_event(UserWindowEvent( + EventData::TemplateUpdated(template), + unsafe { WindowId::dummy() }, + )) + .unwrap(); } Err(err) => { if err.kind() != std::io::ErrorKind::WouldBlock { @@ -47,8 +33,6 @@ pub(crate) fn init(proxy: futures_channel::mpsc::UnboundedSender(root: Component

, props: P, cfg: Config) let proxy = event_loop.create_proxy(); + // Intialize hot reloading if it is enabled + #[cfg(all(feature = "hot-reload", debug_assertions))] + hot_reload::init(proxy.clone()); + // We start the tokio runtime *on this thread* // Any future we poll later will use this runtime to spawn tasks and for IO let rt = tokio::runtime::Builder::new_multi_thread() @@ -168,6 +172,14 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) } Event::UserEvent(event) => match event.0 { + EventData::TemplateUpdated(template) => { + for webview in webviews.values_mut() { + webview.dom.replace_template(template); + + poll_vdom(webview); + } + } + EventData::CloseWindow => { webviews.remove(&event.1); diff --git a/packages/hot-reload-macro/Cargo.toml b/packages/hot-reload-macro/Cargo.toml new file mode 100644 index 000000000..009c6501a --- /dev/null +++ b/packages/hot-reload-macro/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dioxus-hot-reload-macro" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +syn = { version = "1.0.107", features = ["full"] } +proc-macro2 = { version = "1.0" } + +[lib] +proc-macro = true \ No newline at end of file diff --git a/packages/hot-reload-macro/src/lib.rs b/packages/hot-reload-macro/src/lib.rs new file mode 100644 index 000000000..211be00cf --- /dev/null +++ b/packages/hot-reload-macro/src/lib.rs @@ -0,0 +1,7 @@ +use proc_macro::TokenStream; +use syn::__private::quote::quote; + +#[proc_macro] +pub fn hot_reload(_: TokenStream) -> TokenStream { + quote!(dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"))).into() +} diff --git a/packages/hot-reload/Cargo.toml b/packages/hot-reload/Cargo.toml new file mode 100644 index 000000000..5c9f34826 --- /dev/null +++ b/packages/hot-reload/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dioxus-hot-reload" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dioxus-hot-reload-macro = { path = "../hot-reload-macro" } +dioxus-rsx = { path = "../rsx" } +dioxus-core = { path = "../core", features = ["serialize"] } +dioxus-html = { path = "../html", features = ["hot-reload-context"] } + +interprocess = { version = "1.2.1" } +notify = "5.0.0" +chrono = "0.4.23" +serde_json = "1.0.91" diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs new file mode 100644 index 000000000..4918b0c03 --- /dev/null +++ b/packages/hot-reload/src/lib.rs @@ -0,0 +1,84 @@ +use std::{ + io::Write, + path::PathBuf, + str::FromStr, + sync::{Arc, Mutex}, +}; + +pub use dioxus_hot_reload_macro::hot_reload; +use dioxus_html::HtmlCtx; +use dioxus_rsx::hot_reload::{FileMap, UpdateResult}; +use interprocess::local_socket::LocalSocketListener; +use notify::{RecommendedWatcher, RecursiveMode, Watcher}; + +pub fn init(path: &'static str) { + if let Ok(crate_dir) = PathBuf::from_str(path) { + let temp_file = std::env::temp_dir().join("@dioxusin"); + let channels = Arc::new(Mutex::new(Vec::new())); + if let Ok(local_socket_stream) = LocalSocketListener::bind(temp_file.as_path()) { + // listen for connections + std::thread::spawn({ + let channels = channels.clone(); + move || { + for connection in local_socket_stream.incoming() { + if let Ok(connection) = connection { + channels.lock().unwrap().push(connection); + println!("Connected to hot reloading πŸš€"); + } + } + } + }); + + // watch for changes + std::thread::spawn(move || { + let mut last_update_time = chrono::Local::now().timestamp(); + let mut file_map = FileMap::::new(crate_dir.clone()); + + let (tx, rx) = std::sync::mpsc::channel(); + + let mut watcher = RecommendedWatcher::new(tx, notify::Config::default()).unwrap(); + + let mut examples_path = crate_dir.clone(); + examples_path.push("examples"); + let _ = watcher.watch(&examples_path, RecursiveMode::Recursive); + let mut src_path = crate_dir.clone(); + src_path.push("src"); + let _ = watcher.watch(&src_path, RecursiveMode::Recursive); + + for evt in rx { + // Give time for the change to take effect before reading the file + std::thread::sleep(std::time::Duration::from_millis(100)); + if chrono::Local::now().timestamp() > last_update_time { + if let Ok(evt) = evt { + let mut channels = channels.lock().unwrap(); + for path in &evt.paths { + // skip non rust files + if path.extension().and_then(|p| p.to_str()) != Some("rs") { + continue; + } + + // find changes to the rsx in the file + match file_map.update_rsx(&path, crate_dir.as_path()) { + UpdateResult::UpdatedRsx(msgs) => { + for msg in msgs { + for channel in channels.iter_mut() { + let msg = serde_json::to_string(&msg).unwrap(); + channel.write_all(msg.as_bytes()).unwrap(); + channel.write_all(&[b'\n']).unwrap(); + } + } + } + UpdateResult::NeedsRebuild => { + println!("Rebuild needed... shutting down hot reloading"); + return; + } + } + } + } + last_update_time = chrono::Local::now().timestamp(); + } + } + }); + } + } +} From 6eff4438cfb5981e69e641c6d6b880b4f4eab291 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 11 Jan 2023 14:20:38 -0600 Subject: [PATCH 02/44] implement hot reloading for TUI --- packages/tui/Cargo.toml | 9 ++++++++- packages/tui/src/hot_reload.rs | 31 ++++++++++++++++++++++++++++++ packages/tui/src/lib.rs | 35 ++++++++++++++++++++++++++++------ 3 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 packages/tui/src/hot_reload.rs diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 5341d40f4..1e35f8b3e 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -14,7 +14,7 @@ license = "MIT/Apache-2.0" [dependencies] dioxus = { path = "../dioxus", version = "^0.3.0" } -dioxus-core = { path = "../core", version = "^0.3.0" } +dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] } dioxus-html = { path = "../html", version = "^0.3.0" } dioxus-native-core = { path = "../native-core", version = "^0.2.0" } dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" } @@ -29,6 +29,9 @@ smallvec = "1.6" rustc-hash = "1.1.0" anymap = "1.0.0-beta.2" futures-channel = "0.3.25" +interprocess = { version = "1.2.1", optional = true } +serde = { version = "1.0.136", optional = true } +serde_json = { version = "1.0.79", optional = true } [dev-dependencies] dioxus = { path = "../dioxus" } @@ -38,3 +41,7 @@ criterion = "0.3.5" [[bench]] name = "update" harness = false + +[features] +default = ["hot-reload"] +hot-reload = ["interprocess", "serde", "serde_json"] diff --git a/packages/tui/src/hot_reload.rs b/packages/tui/src/hot_reload.rs new file mode 100644 index 000000000..8385879e0 --- /dev/null +++ b/packages/tui/src/hot_reload.rs @@ -0,0 +1,31 @@ +#![allow(dead_code)] + +use dioxus_core::Template; + +use interprocess::local_socket::LocalSocketStream; +use std::io::{BufRead, BufReader}; +use tokio::sync::mpsc::UnboundedSender; + +pub(crate) fn init(proxy: UnboundedSender>) { + std::thread::spawn(move || { + let temp_file = std::env::temp_dir().join("@dioxusin"); + if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) { + let mut buf_reader = BufReader::new(socket); + loop { + let mut buf = String::new(); + match buf_reader.read_line(&mut buf) { + Ok(_) => { + let template: Template<'static> = + serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap(); + proxy.send(template).unwrap(); + } + Err(err) => { + if err.kind() != std::io::ErrorKind::WouldBlock { + break; + } + } + } + } + } + }); +} diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 4c8d8e739..6af02e993 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -22,11 +22,14 @@ use std::{ use std::{io, time::Duration}; use taffy::Taffy; pub use taffy::{geometry::Point, prelude::*}; +use tokio::{select, sync::mpsc::unbounded_channel}; use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; mod config; mod focus; mod hooks; +#[cfg(all(feature = "hot-reload", debug_assertions))] +mod hot_reload; mod layout; mod node; pub mod prelude; @@ -144,6 +147,12 @@ fn render_vdom( .enable_all() .build()? .block_on(async { + #[cfg(all(feature = "hot-reload", debug_assertions))] + let mut hot_reload_rx = { + let (hot_reload_tx, hot_reload_rx) = unbounded_channel::>(); + hot_reload::init(hot_reload_tx); + hot_reload_rx + }; let mut terminal = (!cfg.headless).then(|| { enable_raw_mode().unwrap(); let mut stdout = std::io::stdout(); @@ -223,16 +232,21 @@ fn render_vdom( } } - use futures::future::{select, Either}; + let mut new_templete = None; { let wait = vdom.wait_for_work(); + #[cfg(all(feature = "hot-reload", debug_assertions))] + let hot_reload_wait = hot_reload_rx.recv(); + #[cfg(not(all(feature = "hot-reload", debug_assertions)))] + let hot_reload_wait = std::future::pending(); + pin_mut!(wait); - match select(wait, event_reciever.next()).await { - Either::Left((_a, _b)) => { - // - } - Either::Right((evt, _o)) => { + select! { + _ = wait => { + + }, + evt = event_reciever.next() => { match evt.as_ref().unwrap() { InputEvent::UserInput(event) => match event { TermEvent::Key(key) => { @@ -252,10 +266,19 @@ fn render_vdom( if let InputEvent::UserInput(evt) = evt.unwrap() { register_event(evt); } + }, + Some(template) = hot_reload_wait => { + new_templete = Some(template); } } } + // if we have a new template, replace the old one + if let Some(template) = new_templete { + // println!("reloading template"); + vdom.replace_template(template); + } + { let evts = { let mut rdom = rdom.borrow_mut(); From e5e1abbdacc2916f42e5a57498d7765918e1d94f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 11 Jan 2023 14:41:29 -0600 Subject: [PATCH 03/44] implement hot reloading for liveview --- packages/liveview/Cargo.toml | 11 ++++++---- packages/liveview/src/hot_reload.rs | 31 +++++++++++++++++++++++++++++ packages/liveview/src/lib.rs | 2 ++ packages/liveview/src/pool.rs | 18 +++++++++++++++++ 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 packages/liveview/src/hot_reload.rs diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index 0fc26674c..53df88759 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -13,6 +13,10 @@ license = "MIT/Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +dioxus-html = { path = "../html", features = ["serialize"], version = "^0.3.0" } +dioxus-core = { path = "../core", features = ["serialize"], version = "^0.3.0" } +dioxus-interpreter-js = { path = "../interpreter", version = "0.3.0" } + tokio = { version = "1.23.0", features = ["full"] } futures-util = { version = "0.3.25", default-features = false, features = [ "sink", @@ -25,9 +29,7 @@ serde = { version = "1.0.151", features = ["derive"] } serde_json = "1.0.91" tokio-util = { version = "0.7.4", features = ["full"] } -dioxus-html = { path = "../html", features = ["serialize"], version = "^0.3.0" } -dioxus-core = { path = "../core", features = ["serialize"], version = "^0.3.0" } -dioxus-interpreter-js = { path = "../interpreter", version = "0.3.0" } +interprocess = { version = "1.2.1", optional = true } # warp warp = { version = "0.3.3", optional = true } @@ -56,8 +58,9 @@ salvo = { version = "0.37.7", features = ["affix", "ws"] } tower = "0.4.13" [features] -default = [] +default = ["hot-reload"] # actix = ["actix-files", "actix-web", "actix-ws"] +hot-reload = ["interprocess"] [[example]] name = "axum" diff --git a/packages/liveview/src/hot_reload.rs b/packages/liveview/src/hot_reload.rs new file mode 100644 index 000000000..8385879e0 --- /dev/null +++ b/packages/liveview/src/hot_reload.rs @@ -0,0 +1,31 @@ +#![allow(dead_code)] + +use dioxus_core::Template; + +use interprocess::local_socket::LocalSocketStream; +use std::io::{BufRead, BufReader}; +use tokio::sync::mpsc::UnboundedSender; + +pub(crate) fn init(proxy: UnboundedSender>) { + std::thread::spawn(move || { + let temp_file = std::env::temp_dir().join("@dioxusin"); + if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) { + let mut buf_reader = BufReader::new(socket); + loop { + let mut buf = String::new(); + match buf_reader.read_line(&mut buf) { + Ok(_) => { + let template: Template<'static> = + serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap(); + proxy.send(template).unwrap(); + } + Err(err) => { + if err.kind() != std::io::ErrorKind::WouldBlock { + break; + } + } + } + } + } + }); +} diff --git a/packages/liveview/src/lib.rs b/packages/liveview/src/lib.rs index ffddf0272..6c4428c3c 100644 --- a/packages/liveview/src/lib.rs +++ b/packages/liveview/src/lib.rs @@ -18,6 +18,8 @@ pub mod adapters { pub use adapters::*; +#[cfg(all(feature = "hot-reload", debug_assertions))] +mod hot_reload; pub mod pool; use futures_util::{SinkExt, StreamExt}; pub use pool::*; diff --git a/packages/liveview/src/pool.rs b/packages/liveview/src/pool.rs index a25673d84..0f7598333 100644 --- a/packages/liveview/src/pool.rs +++ b/packages/liveview/src/pool.rs @@ -103,6 +103,13 @@ pub async fn run( where T: Send + 'static, { + #[cfg(all(feature = "hot-reload", debug_assertions))] + let mut hot_reload_rx = { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + crate::hot_reload::init(tx); + rx + }; + let mut vdom = VirtualDom::new_with_props(app, props); // todo: use an efficient binary packed format for this @@ -122,6 +129,11 @@ where } loop { + #[cfg(all(feature = "hot-reload", debug_assertions))] + let hot_reload_wait = hot_reload_rx.recv(); + #[cfg(not(all(feature = "hot-reload", debug_assertions)))] + let hot_reload_wait = std::future::pending(); + tokio::select! { // poll any futures or suspense _ = vdom.wait_for_work() => {} @@ -138,6 +150,12 @@ where None => return Ok(()), } } + + new_template = hot_reload_wait => { + if let Some(new_template) = new_template { + vdom.replace_template(new_template); + } + } } let edits = vdom From 1b53d4585e0bf86c463199e5522d65917cc53eb2 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 11 Jan 2023 14:59:00 -0600 Subject: [PATCH 04/44] handle liveview sockets disconnecting --- packages/hot-reload/src/lib.rs | 53 +++++++++++++++++++++++++---- packages/liveview/src/hot_reload.rs | 4 ++- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 4918b0c03..40438fe62 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -5,6 +5,7 @@ use std::{ sync::{Arc, Mutex}, }; +use dioxus_core::Template; pub use dioxus_hot_reload_macro::hot_reload; use dioxus_html::HtmlCtx; use dioxus_rsx::hot_reload::{FileMap, UpdateResult}; @@ -15,13 +16,30 @@ pub fn init(path: &'static str) { if let Ok(crate_dir) = PathBuf::from_str(path) { let temp_file = std::env::temp_dir().join("@dioxusin"); let channels = Arc::new(Mutex::new(Vec::new())); + let file_map = Arc::new(Mutex::new(FileMap::::new(crate_dir.clone()))); if let Ok(local_socket_stream) = LocalSocketListener::bind(temp_file.as_path()) { // listen for connections std::thread::spawn({ + let file_map = file_map.clone(); let channels = channels.clone(); move || { for connection in local_socket_stream.incoming() { - if let Ok(connection) = connection { + if let Ok(mut connection) = connection { + // send any templates than have changed before the socket connected + let templates: Vec<_> = { + file_map + .lock() + .unwrap() + .map + .values() + .filter_map(|(_, template_slot)| *template_slot) + .collect() + }; + for template in templates { + if !send_template(template, &mut connection) { + continue; + } + } channels.lock().unwrap().push(connection); println!("Connected to hot reloading πŸš€"); } @@ -32,7 +50,6 @@ pub fn init(path: &'static str) { // watch for changes std::thread::spawn(move || { let mut last_update_time = chrono::Local::now().timestamp(); - let mut file_map = FileMap::::new(crate_dir.clone()); let (tx, rx) = std::sync::mpsc::channel(); @@ -58,13 +75,21 @@ pub fn init(path: &'static str) { } // find changes to the rsx in the file - match file_map.update_rsx(&path, crate_dir.as_path()) { + match file_map + .lock() + .unwrap() + .update_rsx(&path, crate_dir.as_path()) + { UpdateResult::UpdatedRsx(msgs) => { for msg in msgs { - for channel in channels.iter_mut() { - let msg = serde_json::to_string(&msg).unwrap(); - channel.write_all(msg.as_bytes()).unwrap(); - channel.write_all(&[b'\n']).unwrap(); + let mut i = 0; + while i < channels.len() { + let channel = &mut channels[i]; + if send_template(msg, channel) { + i += 1; + } else { + channels.remove(i); + } } } } @@ -82,3 +107,17 @@ pub fn init(path: &'static str) { } } } + +fn send_template(template: Template<'static>, channel: &mut impl Write) -> bool { + if let Ok(msg) = serde_json::to_string(&template) { + if channel.write_all(msg.as_bytes()).is_err() { + return false; + } + if channel.write_all(&[b'\n']).is_err() { + return false; + } + true + } else { + false + } +} diff --git a/packages/liveview/src/hot_reload.rs b/packages/liveview/src/hot_reload.rs index 8385879e0..17a8142c4 100644 --- a/packages/liveview/src/hot_reload.rs +++ b/packages/liveview/src/hot_reload.rs @@ -17,7 +17,9 @@ pub(crate) fn init(proxy: UnboundedSender>) { Ok(_) => { let template: Template<'static> = serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap(); - proxy.send(template).unwrap(); + if proxy.send(template).is_err() { + return; + } } Err(err) => { if err.kind() != std::io::ErrorKind::WouldBlock { From 042b67b61b901fb75a648c1726e91eac4392771a Mon Sep 17 00:00:00 2001 From: = Date: Wed, 11 Jan 2023 16:43:41 -0600 Subject: [PATCH 05/44] factor out a hot reloading connect function --- packages/desktop/Cargo.toml | 1 + packages/desktop/src/hot_reload.rs | 38 ----------------------------- packages/desktop/src/lib.rs | 13 +++++++--- packages/hot-reload/src/lib.rs | 31 +++++++++++++++++++++-- packages/liveview/Cargo.toml | 1 + packages/liveview/src/hot_reload.rs | 31 ----------------------- packages/liveview/src/lib.rs | 2 -- packages/liveview/src/pool.rs | 4 ++- packages/tui/Cargo.toml | 1 + packages/tui/src/hot_reload.rs | 31 ----------------------- packages/tui/src/lib.rs | 6 ++--- 11 files changed, 47 insertions(+), 112 deletions(-) delete mode 100644 packages/desktop/src/hot_reload.rs delete mode 100644 packages/liveview/src/hot_reload.rs delete mode 100644 packages/tui/src/hot_reload.rs diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 16cc622c6..eb2f88fc9 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -15,6 +15,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] } dioxus-html = { path = "../html", features = ["serialize"], version = "^0.3.0" } dioxus-interpreter-js = { path = "../interpreter", version = "^0.3.0" } +dioxus-hot-reload = { path = "../hot-reload" } serde = "1.0.136" serde_json = "1.0.79" diff --git a/packages/desktop/src/hot_reload.rs b/packages/desktop/src/hot_reload.rs deleted file mode 100644 index 20907f59d..000000000 --- a/packages/desktop/src/hot_reload.rs +++ /dev/null @@ -1,38 +0,0 @@ -#![allow(dead_code)] - -use dioxus_core::Template; - -use interprocess::local_socket::LocalSocketStream; -use std::io::{BufRead, BufReader}; -use wry::application::{event_loop::EventLoopProxy, window::WindowId}; - -use crate::desktop_context::{EventData, UserWindowEvent}; - -pub(crate) fn init(proxy: EventLoopProxy) { - std::thread::spawn(move || { - let temp_file = std::env::temp_dir().join("@dioxusin"); - if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) { - let mut buf_reader = BufReader::new(socket); - loop { - let mut buf = String::new(); - match buf_reader.read_line(&mut buf) { - Ok(_) => { - let template: Template<'static> = - serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap(); - proxy - .send_event(UserWindowEvent( - EventData::TemplateUpdated(template), - unsafe { WindowId::dummy() }, - )) - .unwrap(); - } - Err(err) => { - if err.kind() != std::io::ErrorKind::WouldBlock { - break; - } - } - } - } - } - }); -} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 7072f1dd0..0ec1f922c 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -12,9 +12,6 @@ mod protocol; mod waker; mod webview; -#[cfg(all(feature = "hot-reload", debug_assertions))] -mod hot_reload; - pub use cfg::Config; pub use desktop_context::{use_window, DesktopContext}; use desktop_context::{EventData, UserWindowEvent, WebviewQueue}; @@ -111,7 +108,15 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) // Intialize hot reloading if it is enabled #[cfg(all(feature = "hot-reload", debug_assertions))] - hot_reload::init(proxy.clone()); + { + let proxy = proxy.clone(); + dioxus_hot_reload::connect(move |template| { + let _ = proxy.send_event(UserWindowEvent( + EventData::TemplateUpdated(template), + unsafe { WindowId::dummy() }, + )); + }); + } // We start the tokio runtime *on this thread* // Any future we poll later will use this runtime to spawn tasks and for IO diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 4918b0c03..554263c5f 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -1,16 +1,18 @@ use std::{ - io::Write, + io::{BufRead, BufReader, Write}, path::PathBuf, str::FromStr, sync::{Arc, Mutex}, }; +use dioxus_core::Template; pub use dioxus_hot_reload_macro::hot_reload; use dioxus_html::HtmlCtx; use dioxus_rsx::hot_reload::{FileMap, UpdateResult}; -use interprocess::local_socket::LocalSocketListener; +use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; +/// Initialize the hot reloading listener on the given path pub fn init(path: &'static str) { if let Ok(crate_dir) = PathBuf::from_str(path) { let temp_file = std::env::temp_dir().join("@dioxusin"); @@ -82,3 +84,28 @@ pub fn init(path: &'static str) { } } } + +/// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected +pub fn connect(mut f: impl FnMut(Template<'static>) + Send + 'static) { + std::thread::spawn(move || { + let temp_file = std::env::temp_dir().join("@dioxusin"); + if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) { + let mut buf_reader = BufReader::new(socket); + loop { + let mut buf = String::new(); + match buf_reader.read_line(&mut buf) { + Ok(_) => { + let template: Template<'static> = + serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap(); + f(template); + } + Err(err) => { + if err.kind() != std::io::ErrorKind::WouldBlock { + break; + } + } + } + } + } + }); +} diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index 53df88759..08a2b757c 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -16,6 +16,7 @@ license = "MIT/Apache-2.0" dioxus-html = { path = "../html", features = ["serialize"], version = "^0.3.0" } dioxus-core = { path = "../core", features = ["serialize"], version = "^0.3.0" } dioxus-interpreter-js = { path = "../interpreter", version = "0.3.0" } +dioxus-hot-reload = { path = "../hot-reload" } tokio = { version = "1.23.0", features = ["full"] } futures-util = { version = "0.3.25", default-features = false, features = [ diff --git a/packages/liveview/src/hot_reload.rs b/packages/liveview/src/hot_reload.rs deleted file mode 100644 index 8385879e0..000000000 --- a/packages/liveview/src/hot_reload.rs +++ /dev/null @@ -1,31 +0,0 @@ -#![allow(dead_code)] - -use dioxus_core::Template; - -use interprocess::local_socket::LocalSocketStream; -use std::io::{BufRead, BufReader}; -use tokio::sync::mpsc::UnboundedSender; - -pub(crate) fn init(proxy: UnboundedSender>) { - std::thread::spawn(move || { - let temp_file = std::env::temp_dir().join("@dioxusin"); - if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) { - let mut buf_reader = BufReader::new(socket); - loop { - let mut buf = String::new(); - match buf_reader.read_line(&mut buf) { - Ok(_) => { - let template: Template<'static> = - serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap(); - proxy.send(template).unwrap(); - } - Err(err) => { - if err.kind() != std::io::ErrorKind::WouldBlock { - break; - } - } - } - } - } - }); -} diff --git a/packages/liveview/src/lib.rs b/packages/liveview/src/lib.rs index 6c4428c3c..ffddf0272 100644 --- a/packages/liveview/src/lib.rs +++ b/packages/liveview/src/lib.rs @@ -18,8 +18,6 @@ pub mod adapters { pub use adapters::*; -#[cfg(all(feature = "hot-reload", debug_assertions))] -mod hot_reload; pub mod pool; use futures_util::{SinkExt, StreamExt}; pub use pool::*; diff --git a/packages/liveview/src/pool.rs b/packages/liveview/src/pool.rs index 0f7598333..3def78537 100644 --- a/packages/liveview/src/pool.rs +++ b/packages/liveview/src/pool.rs @@ -106,7 +106,9 @@ where #[cfg(all(feature = "hot-reload", debug_assertions))] let mut hot_reload_rx = { let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - crate::hot_reload::init(tx); + dioxus_hot_reload::connect(move |template| { + let _ = tx.send(template); + }); rx }; diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 1e35f8b3e..d6c96cfd3 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -18,6 +18,7 @@ dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] } dioxus-html = { path = "../html", version = "^0.3.0" } dioxus-native-core = { path = "../native-core", version = "^0.2.0" } dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" } +dioxus-hot-reload = { path = "../hot-reload" } tui = "0.17.0" crossterm = "0.23.0" diff --git a/packages/tui/src/hot_reload.rs b/packages/tui/src/hot_reload.rs deleted file mode 100644 index 8385879e0..000000000 --- a/packages/tui/src/hot_reload.rs +++ /dev/null @@ -1,31 +0,0 @@ -#![allow(dead_code)] - -use dioxus_core::Template; - -use interprocess::local_socket::LocalSocketStream; -use std::io::{BufRead, BufReader}; -use tokio::sync::mpsc::UnboundedSender; - -pub(crate) fn init(proxy: UnboundedSender>) { - std::thread::spawn(move || { - let temp_file = std::env::temp_dir().join("@dioxusin"); - if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) { - let mut buf_reader = BufReader::new(socket); - loop { - let mut buf = String::new(); - match buf_reader.read_line(&mut buf) { - Ok(_) => { - let template: Template<'static> = - serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap(); - proxy.send(template).unwrap(); - } - Err(err) => { - if err.kind() != std::io::ErrorKind::WouldBlock { - break; - } - } - } - } - } - }); -} diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 6af02e993..a8cd295d3 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -28,8 +28,6 @@ use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; mod config; mod focus; mod hooks; -#[cfg(all(feature = "hot-reload", debug_assertions))] -mod hot_reload; mod layout; mod node; pub mod prelude; @@ -150,7 +148,9 @@ fn render_vdom( #[cfg(all(feature = "hot-reload", debug_assertions))] let mut hot_reload_rx = { let (hot_reload_tx, hot_reload_rx) = unbounded_channel::>(); - hot_reload::init(hot_reload_tx); + dioxus_hot_reload::connect(move |template| { + let _ = hot_reload_tx.send(template); + }); hot_reload_rx }; let mut terminal = (!cfg.headless).then(|| { From 9e45cbe4a779507ba2308bb86130ab86ae538f99 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 11 Jan 2023 18:51:09 -0600 Subject: [PATCH 06/44] remove the hot-reload-macro crate --- Cargo.toml | 1 - packages/hot-reload-macro/Cargo.toml | 13 ------------- packages/hot-reload-macro/src/lib.rs | 7 ------- packages/hot-reload/Cargo.toml | 1 - packages/hot-reload/src/lib.rs | 14 ++++++++++++-- 5 files changed, 12 insertions(+), 24 deletions(-) delete mode 100644 packages/hot-reload-macro/Cargo.toml delete mode 100644 packages/hot-reload-macro/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index ebfe288c5..c00f8f948 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ members = [ "packages/rsx-rosetta", "packages/signals", "packages/hot-reload", - "packages/hot-reload-macro", "docs/guide", ] diff --git a/packages/hot-reload-macro/Cargo.toml b/packages/hot-reload-macro/Cargo.toml deleted file mode 100644 index 009c6501a..000000000 --- a/packages/hot-reload-macro/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "dioxus-hot-reload-macro" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -syn = { version = "1.0.107", features = ["full"] } -proc-macro2 = { version = "1.0" } - -[lib] -proc-macro = true \ No newline at end of file diff --git a/packages/hot-reload-macro/src/lib.rs b/packages/hot-reload-macro/src/lib.rs deleted file mode 100644 index 211be00cf..000000000 --- a/packages/hot-reload-macro/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -use proc_macro::TokenStream; -use syn::__private::quote::quote; - -#[proc_macro] -pub fn hot_reload(_: TokenStream) -> TokenStream { - quote!(dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"))).into() -} diff --git a/packages/hot-reload/Cargo.toml b/packages/hot-reload/Cargo.toml index 5c9f34826..d6088a109 100644 --- a/packages/hot-reload/Cargo.toml +++ b/packages/hot-reload/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-hot-reload-macro = { path = "../hot-reload-macro" } dioxus-rsx = { path = "../rsx" } dioxus-core = { path = "../core", features = ["serialize"] } dioxus-html = { path = "../html", features = ["hot-reload-context"] } diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 554263c5f..14cc8e9c2 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -6,14 +6,13 @@ use std::{ }; use dioxus_core::Template; -pub use dioxus_hot_reload_macro::hot_reload; use dioxus_html::HtmlCtx; use dioxus_rsx::hot_reload::{FileMap, UpdateResult}; use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; /// Initialize the hot reloading listener on the given path -pub fn init(path: &'static str) { +pub fn init(path: &'static str, listening_paths: &'static [&'static str]) { if let Ok(crate_dir) = PathBuf::from_str(path) { let temp_file = std::env::temp_dir().join("@dioxusin"); let channels = Arc::new(Mutex::new(Vec::new())); @@ -109,3 +108,14 @@ pub fn connect(mut f: impl FnMut(Template<'static>) + Send + 'static) { } }); } + +#[macro_export] +macro_rules! hot_reload { + () => { + dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &[]) + }; + + ($($paths: literal,)*,?) => { + dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &[$($path,)*]) + }; +} From 19aaa0ee5b1e68ba428d1f7dc6e295592084d023 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 11 Jan 2023 21:06:00 -0600 Subject: [PATCH 07/44] allow watching arbitrary paths --- packages/hot-reload/src/lib.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index e0c547578..586c3c95b 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -12,8 +12,8 @@ use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; /// Initialize the hot reloading listener on the given path -pub fn init(path: &'static str, listening_paths: &'static [&'static str]) { - if let Ok(crate_dir) = PathBuf::from_str(path) { +pub fn init(root_path: &'static str, listening_paths: &'static [&'static str]) { + if let Ok(crate_dir) = PathBuf::from_str(root_path) { let temp_file = std::env::temp_dir().join("@dioxusin"); let channels = Arc::new(Mutex::new(Vec::new())); let file_map = Arc::new(Mutex::new(FileMap::::new(crate_dir.clone()))); @@ -55,12 +55,12 @@ pub fn init(path: &'static str, listening_paths: &'static [&'static str]) { let mut watcher = RecommendedWatcher::new(tx, notify::Config::default()).unwrap(); - let mut examples_path = crate_dir.clone(); - examples_path.push("examples"); - let _ = watcher.watch(&examples_path, RecursiveMode::Recursive); - let mut src_path = crate_dir.clone(); - src_path.push("src"); - let _ = watcher.watch(&src_path, RecursiveMode::Recursive); + for path in listening_paths { + if let Ok(path) = PathBuf::from_str(path) { + let examples_path = crate_dir.join(path); + let _ = watcher.watch(&examples_path, RecursiveMode::Recursive); + } + } for evt in rx { // Give time for the change to take effect before reading the file @@ -147,13 +147,17 @@ pub fn connect(mut f: impl FnMut(Template<'static>) + Send + 'static) { }); } +/// Start the hot reloading server +/// +/// Pass any number of paths to listen for changes on relative to the crate root as strings. +/// If no paths are passed, it will listen on the src and examples folders. #[macro_export] macro_rules! hot_reload { () => { - dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &[]) + dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &["src", "examples"]) }; - ($($paths: literal,)*,?) => { - dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &[$($path,)*]) + ($($paths: literal),*) => { + dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &[$($paths),*]) }; } From 82048737f4a1b2fece22b92ae70b97d6dc409c7d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 12 Jan 2023 08:47:33 -0600 Subject: [PATCH 08/44] fix the macro export and WIP hot reloading docs --- docs/guide/src/en/SUMMARY.md | 2 +- .../src/en/getting_started/hot_reload.md | 49 ++++++++++++++++--- packages/dioxus/Cargo.toml | 4 +- packages/dioxus/src/lib.rs | 3 ++ packages/hot-reload/src/lib.rs | 4 +- 5 files changed, 51 insertions(+), 11 deletions(-) diff --git a/docs/guide/src/en/SUMMARY.md b/docs/guide/src/en/SUMMARY.md index 83840fe90..f9f31ed52 100644 --- a/docs/guide/src/en/SUMMARY.md +++ b/docs/guide/src/en/SUMMARY.md @@ -5,11 +5,11 @@ - [Getting Started](getting_started/index.md) - [Desktop](getting_started/desktop.md) - [Web](getting_started/web.md) - - [Hot Reload](getting_started/hot_reload.md) - [Server-Side Rendering](getting_started/ssr.md) - [Liveview](getting_started/liveview.md) - [Terminal UI](getting_started/tui.md) - [Mobile](getting_started/mobile.md) + - [Hot Reloading](getting_started/hot_reload.md) - [Describing the UI](describing_ui/index.md) - [Special Attributes](describing_ui/special_attributes.md) - [Components](describing_ui/components.md) diff --git a/docs/guide/src/en/getting_started/hot_reload.md b/docs/guide/src/en/getting_started/hot_reload.md index 823baa179..8c4d4310d 100644 --- a/docs/guide/src/en/getting_started/hot_reload.md +++ b/docs/guide/src/en/getting_started/hot_reload.md @@ -2,20 +2,53 @@ 1. Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits. 2. It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program. -3. Currently the cli only implements hot reloading for the web renderer. +3. Currently the cli only implements hot reloading for the web renderer. For TUI, desktop, and liveview you can use the hot reload macro. + +# Web + +For the web renderer, you can use the dioxus cli to serve your application with hot reloading enabled. + +## Setup -# Setup Install [dioxus-cli](https://github.com/DioxusLabs/cli). Hot reloading is automatically enabled when using the web renderer on debug builds. -# Usage -1. run: -``` +## Usage + +1. Run: +```bash dioxus serve --hot-reload ``` -2. change some code within a rsx macro -3. open your localhost in a browser -4. save and watch the style change without recompiling +2. Change some code within a rsx or render macro +3. Open your localhost in a browser +4. Save and watch the style change without recompiling + +# Desktop/Liveview/TUI + +For desktop, LiveView, and tui, you can place the hot reload macro before your app runs to enable hot reloading. + +## Setup + +Add the following to your main function: + +```rust +fn main() { + dioxus::hot_reload_init!(); + // launch your application +} +``` + +## Usage +1. Run: +```bash +cargo run +``` +2. Change some code within a rsx or render macro +3. Save and watch the style change without recompiling + +## Custom renders + +For custom renderers # Limitations 1. The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will trigger a full recompile to capture the expression. diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index ceab90a1b..0360b207f 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -17,12 +17,14 @@ dioxus-html = { path = "../html", version = "^0.3.0", optional = true } dioxus-core-macro = { path = "../core-macro", version = "^0.3.0", optional = true } dioxus-hooks = { path = "../hooks", version = "^0.3.0", optional = true } dioxus-rsx = { path = "../rsx", version = "0.0.2", optional = true } +dioxus-hot-reload = { path = "../hot-reload", version = "0.1.0", optional = true } [features] -default = ["macro", "hooks", "html"] +default = ["macro", "hooks", "html", "hot-reload"] macro = ["dioxus-core-macro", "dioxus-rsx"] html = ["dioxus-html"] hooks = ["dioxus-hooks"] +hot-reload = ["dioxus-hot-reload"] [dev-dependencies] diff --git a/packages/dioxus/src/lib.rs b/packages/dioxus/src/lib.rs index fed3d2bd5..fe181bf14 100644 --- a/packages/dioxus/src/lib.rs +++ b/packages/dioxus/src/lib.rs @@ -31,4 +31,7 @@ pub mod prelude { #[cfg(feature = "html")] pub use dioxus_elements::{prelude::*, GlobalAttributes, SvgAttributes}; + + #[cfg(feature = "hot-reload")] + pub use dioxus_hot_reload::hot_reload_init; } diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 586c3c95b..6ac5c2c35 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -152,7 +152,7 @@ pub fn connect(mut f: impl FnMut(Template<'static>) + Send + 'static) { /// Pass any number of paths to listen for changes on relative to the crate root as strings. /// If no paths are passed, it will listen on the src and examples folders. #[macro_export] -macro_rules! hot_reload { +macro_rules! hot_reload_init_inner { () => { dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &["src", "examples"]) }; @@ -161,3 +161,5 @@ macro_rules! hot_reload { dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &[$($paths),*]) }; } + +pub use hot_reload_init_inner as hot_reload_init; From 9be1df1451d2093bec76d8d099ca299eb4c1d952 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 12 Jan 2023 10:32:45 -0600 Subject: [PATCH 09/44] allow disabling logging --- .../src/en/getting_started/hot_reload.md | 12 ++---- packages/hot-reload/src/lib.rs | 38 ++++++++++++++----- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/docs/guide/src/en/getting_started/hot_reload.md b/docs/guide/src/en/getting_started/hot_reload.md index 8c4d4310d..bd0cb65ac 100644 --- a/docs/guide/src/en/getting_started/hot_reload.md +++ b/docs/guide/src/en/getting_started/hot_reload.md @@ -2,7 +2,7 @@ 1. Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits. 2. It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program. -3. Currently the cli only implements hot reloading for the web renderer. For TUI, desktop, and liveview you can use the hot reload macro. +3. Currently the cli only implements hot reloading for the web renderer. For TUI, desktop, and LiveView you can use the hot reload macro instead. # Web @@ -25,7 +25,7 @@ dioxus serve --hot-reload # Desktop/Liveview/TUI -For desktop, LiveView, and tui, you can place the hot reload macro before your app runs to enable hot reloading. +For desktop, LiveView, and tui, you can place the hot reload macro at the top of your main function to enable hot reloading. ## Setup @@ -33,7 +33,7 @@ Add the following to your main function: ```rust fn main() { - dioxus::hot_reload_init!(); + hot_reload_init!(); // launch your application } ``` @@ -46,10 +46,6 @@ cargo run 2. Change some code within a rsx or render macro 3. Save and watch the style change without recompiling -## Custom renders - -For custom renderers - # Limitations 1. The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will trigger a full recompile to capture the expression. -2. Components and Iterators can contain arbitrary rust code and will trigger a full recompile when changed. \ No newline at end of file +2. Components and Iterators can contain arbitrary rust code and will trigger a full recompile when changed. diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 6ac5c2c35..36ba0eaf9 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -12,7 +12,7 @@ use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; /// Initialize the hot reloading listener on the given path -pub fn init(root_path: &'static str, listening_paths: &'static [&'static str]) { +pub fn init(root_path: &'static str, listening_paths: &'static [&'static str], log: bool) { if let Ok(crate_dir) = PathBuf::from_str(root_path) { let temp_file = std::env::temp_dir().join("@dioxusin"); let channels = Arc::new(Mutex::new(Vec::new())); @@ -41,7 +41,9 @@ pub fn init(root_path: &'static str, listening_paths: &'static [&'static str]) { } } channels.lock().unwrap().push(connection); - println!("Connected to hot reloading πŸš€"); + if log { + println!("Connected to hot reloading πŸš€"); + } } } } @@ -94,7 +96,11 @@ pub fn init(root_path: &'static str, listening_paths: &'static [&'static str]) { } } UpdateResult::NeedsRebuild => { - println!("Rebuild needed... shutting down hot reloading"); + if log { + println!( + "Rebuild needed... shutting down hot reloading" + ); + } return; } } @@ -152,14 +158,26 @@ pub fn connect(mut f: impl FnMut(Template<'static>) + Send + 'static) { /// Pass any number of paths to listen for changes on relative to the crate root as strings. /// If no paths are passed, it will listen on the src and examples folders. #[macro_export] -macro_rules! hot_reload_init_inner { - () => { - dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &["src", "examples"]) +macro_rules! hot_reload_init { + ($($t: ident)*) => { + #[cfg(debug_assertions)] + dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &["src", "examples"], hot_reload_init!(log: $($t)*)) }; - ($($paths: literal),*) => { - dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &[$($paths),*]) + ($($paths: literal),* $(,)? $($t: ident)*) => { + #[cfg(debug_assertions)] + dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &[$($paths),*], hot_reload_init!(log: $($t)*)) + }; + + (log:) => { + false + }; + + (log: enable logging) => { + true + }; + + (log: disable logging) => { + false }; } - -pub use hot_reload_init_inner as hot_reload_init; From c49c59b8b225ee1eee4e37c92b03251401877985 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 12 Jan 2023 10:32:50 -0600 Subject: [PATCH 10/44] add readme --- packages/hot-reload/README.md | 142 ++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 packages/hot-reload/README.md diff --git a/packages/hot-reload/README.md b/packages/hot-reload/README.md new file mode 100644 index 000000000..fed458361 --- /dev/null +++ b/packages/hot-reload/README.md @@ -0,0 +1,142 @@ +# `dioxus-hot-reload`: Hot Reloading Utilites for Dioxus + + +[![Crates.io][crates-badge]][crates-url] +[![MIT licensed][mit-badge]][mit-url] +[![Build Status][actions-badge]][actions-url] +[![Discord chat][discord-badge]][discord-url] + +[crates-badge]: https://img.shields.io/crates/v/dioxus-hot-reload.svg +[crates-url]: https://crates.io/crates/dioxus-hot-reload + +[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg +[mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE + +[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg +[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster + +[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square +[discord-url]: https://discord.gg/XgGxMSkvUM + +[Website](https://dioxuslabs.com) | +[Guides](https://dioxuslabs.com/guide/) | +[API Docs](https://docs.rs/dioxus-hot-reload/latest/dioxus_hot_reload) | +[Chat](https://discord.gg/XgGxMSkvUM) + + +## Overview + +Dioxus supports hot reloading for static parts of rsx macros. This enables changing the styling of your application without recompiling the rust code. This is useful for rapid iteration on the styling of your application. + + +Hot reloading could update the following change without recompiling: +```rust +rsx! { + div { + "Count: {count}", + } +} +``` +=> +```rust +rsx! { + div { + color: "red", + font_size: "2em", + "Count: {count}", + } +} +``` + +But it could not update the following change: +```rust +rsx! { + div { + "Count: {count}", + } +} +``` +=> +```rust +rsx! { + div { + "Count: {count*2}", + onclick: |_| println!("clicked"), + } +} +``` + +## Usage + +> For hot relaoding with the web renderer, see the [dioxus-cli](https://github.com/DioxusLabs/cli) project. + +For renderers that support hot reloading add this to your main function before you launch your app to start the hot reloading server: + +```rust +fn main(){ + hot_reload_init!(); + // launch your application +} +``` + +The dev server watches on the `src` and `examples` folders in the crate directory by default. To watch on custom paths pass the paths into the hot relaod macro: + +```rust +fn main(){ + hot_reload_init!("src", "examples", "assets"); + // launch your application +} +``` + +By default the hot reloading server will output some logs in the console, to disable these logs pass the `disable logging` flag into the macro: + +```rust +fn main(){ + hot_reload_init!("src", "examples", "assets", disable logging); + // launch your application +} +``` + +## Implementing hot reloading for a custom renderer + +To add hot reloading support to your custom renderer you can use the connect function. This will connect to the dev server you just need to provide a way to transfer `Template`s to the `VirtualDom`. Once you implement this your users can use the hot_reload_init function just like any other render. + +```rust +async fn launch(app: Component) { + let mut vdom = VirtualDom::new(app); + // ... + + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + dioxus_hot_reload::connect(move |template| { + let _ = tx.send(template); + }); + + loop { + tokio::select! { + Some(template) = rx.recv() => { + // update the template in the virtual dom + vdom.replace_template(template); + } + _ = vdom.wait_for_work() => { + // ... + } + } + let mutations = vdom.render_immediate(); + // apply the mutations to the dom + } +} +``` + +## Contributing + +- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues). +- Join the discord and ask questions! + +## License +This project is licensed under the [MIT license]. + +[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Dioxus by you shall be licensed as MIT without any additional +terms or conditions. From 351b9fab6f37d2bce3359ac25cabc397e4295cc7 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 12 Jan 2023 10:52:24 -0600 Subject: [PATCH 11/44] allow custom namespaces --- Cargo.toml | 1 - .../src/en/getting_started/hot_reload.md | 1 + packages/dioxus/src/lib.rs | 2 +- packages/hot-reload/README.md | 11 ++++++ packages/hot-reload/src/lib.rs | 39 +++++++++++++------ 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c00f8f948..e32940e18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,6 @@ publish = false dioxus = { path = "./packages/dioxus" } dioxus-desktop = { path = "./packages/desktop", features = ["transparent"] } dioxus-ssr = { path = "./packages/ssr" } -dioxus-hot-reload = { path = "./packages/hot-reload" } dioxus-router = { path = "./packages/router" } dioxus-signals = { path = "./packages/signals" } fermi = { path = "./packages/fermi" } diff --git a/docs/guide/src/en/getting_started/hot_reload.md b/docs/guide/src/en/getting_started/hot_reload.md index bd0cb65ac..f2b12c983 100644 --- a/docs/guide/src/en/getting_started/hot_reload.md +++ b/docs/guide/src/en/getting_started/hot_reload.md @@ -26,6 +26,7 @@ dioxus serve --hot-reload # Desktop/Liveview/TUI For desktop, LiveView, and tui, you can place the hot reload macro at the top of your main function to enable hot reloading. +Hot reloading is automatically enabled on debug builds. ## Setup diff --git a/packages/dioxus/src/lib.rs b/packages/dioxus/src/lib.rs index fe181bf14..4a7d41ea6 100644 --- a/packages/dioxus/src/lib.rs +++ b/packages/dioxus/src/lib.rs @@ -33,5 +33,5 @@ pub mod prelude { pub use dioxus_elements::{prelude::*, GlobalAttributes, SvgAttributes}; #[cfg(feature = "hot-reload")] - pub use dioxus_hot_reload::hot_reload_init; + pub use dioxus_hot_reload::{self, hot_reload_init}; } diff --git a/packages/hot-reload/README.md b/packages/hot-reload/README.md index fed458361..534bbbc98 100644 --- a/packages/hot-reload/README.md +++ b/packages/hot-reload/README.md @@ -97,6 +97,17 @@ fn main(){ } ``` +If you are using a namespace other than html, you can implement the [HotReloadingContext](https://docs.rs/dioxus-rsx/latest/dioxus_rsx/trait.HotReloadingContext.html) trait to provide a mapping between the rust names of your elements/attributes and the resultsing strings. + +You can then provide the Context to the macro to make hot reloading work with your custom namespace: + +```rust +fn main(){ + hot_reload_init!(@MyNamespace /*more configeration*/); + // launch your application +} +``` + ## Implementing hot reloading for a custom renderer To add hot reloading support to your custom renderer you can use the connect function. This will connect to the dev server you just need to provide a way to transfer `Template`s to the `VirtualDom`. Once you implement this your users can use the hot_reload_init function just like any other render. diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 36ba0eaf9..ef87d4d21 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -6,17 +6,26 @@ use std::{ }; use dioxus_core::Template; -use dioxus_html::HtmlCtx; -use dioxus_rsx::hot_reload::{FileMap, UpdateResult}; +use dioxus_rsx::{ + hot_reload::{FileMap, UpdateResult}, + HotReloadingContext, +}; use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; +#[cfg(debug_assertions)] +pub use dioxus_html::HtmlCtx; + /// Initialize the hot reloading listener on the given path -pub fn init(root_path: &'static str, listening_paths: &'static [&'static str], log: bool) { +pub fn init( + root_path: &'static str, + listening_paths: &'static [&'static str], + log: bool, +) { if let Ok(crate_dir) = PathBuf::from_str(root_path) { let temp_file = std::env::temp_dir().join("@dioxusin"); let channels = Arc::new(Mutex::new(Vec::new())); - let file_map = Arc::new(Mutex::new(FileMap::::new(crate_dir.clone()))); + let file_map = Arc::new(Mutex::new(FileMap::::new(crate_dir.clone()))); if let Ok(local_socket_stream) = LocalSocketListener::bind(temp_file.as_path()) { // listen for connections std::thread::spawn({ @@ -159,25 +168,33 @@ pub fn connect(mut f: impl FnMut(Template<'static>) + Send + 'static) { /// If no paths are passed, it will listen on the src and examples folders. #[macro_export] macro_rules! hot_reload_init { - ($($t: ident)*) => { + ($(@ $ctx:ident)? $($t: ident)*) => { #[cfg(debug_assertions)] - dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &["src", "examples"], hot_reload_init!(log: $($t)*)) + dioxus_hot_reload::init::(core::env!("CARGO_MANIFEST_DIR"), &["src", "examples"], hot_reload_init!(@log: $($t)*)) }; - ($($paths: literal),* $(,)? $($t: ident)*) => { + ($(@ $ctx:ident)? $($paths: literal),* $(,)? $($t: ident)*) => { #[cfg(debug_assertions)] - dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &[$($paths),*], hot_reload_init!(log: $($t)*)) + dioxus_hot_reload::init::(core::env!("CARGO_MANIFEST_DIR"), &[$($paths),*], hot_reload_init!(@log: $($t)*)) }; - (log:) => { + (@log:) => { false }; - (log: enable logging) => { + (@log: enable logging) => { true }; - (log: disable logging) => { + (@log: disable logging) => { false }; + + (@ctx: $ctx: ident) => { + $ctx + }; + + (@ctx: ) => { + dioxus_hot_reload::HtmlCtx + }; } From 6b4a300880b2ae1807949c5c6a2c4ae1a6798994 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 12 Jan 2023 10:58:12 -0600 Subject: [PATCH 12/44] doc formatting and cleanup --- docs/guide/src/en/getting_started/hot_reload.md | 9 ++------- packages/hot-reload/README.md | 8 ++++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/guide/src/en/getting_started/hot_reload.md b/docs/guide/src/en/getting_started/hot_reload.md index f2b12c983..9700752f4 100644 --- a/docs/guide/src/en/getting_started/hot_reload.md +++ b/docs/guide/src/en/getting_started/hot_reload.md @@ -5,16 +5,13 @@ 3. Currently the cli only implements hot reloading for the web renderer. For TUI, desktop, and LiveView you can use the hot reload macro instead. # Web - For the web renderer, you can use the dioxus cli to serve your application with hot reloading enabled. ## Setup - Install [dioxus-cli](https://github.com/DioxusLabs/cli). Hot reloading is automatically enabled when using the web renderer on debug builds. ## Usage - 1. Run: ```bash dioxus serve --hot-reload @@ -24,12 +21,10 @@ dioxus serve --hot-reload 4. Save and watch the style change without recompiling # Desktop/Liveview/TUI - For desktop, LiveView, and tui, you can place the hot reload macro at the top of your main function to enable hot reloading. Hot reloading is automatically enabled on debug builds. ## Setup - Add the following to your main function: ```rust @@ -48,5 +43,5 @@ cargo run 3. Save and watch the style change without recompiling # Limitations -1. The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will trigger a full recompile to capture the expression. -2. Components and Iterators can contain arbitrary rust code and will trigger a full recompile when changed. +1. The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will require a full recompile to capture the expression. +2. Components, Iterators, and some attributes can contain arbitrary rust code and will trigger a full recompile when changed. diff --git a/packages/hot-reload/README.md b/packages/hot-reload/README.md index 534bbbc98..4e8c7d880 100644 --- a/packages/hot-reload/README.md +++ b/packages/hot-reload/README.md @@ -68,9 +68,9 @@ rsx! { ## Usage -> For hot relaoding with the web renderer, see the [dioxus-cli](https://github.com/DioxusLabs/cli) project. +> This crate implements hot reloading for native compilation targets not WASM. For hot relaoding with the web renderer, see the [dioxus-cli](https://github.com/DioxusLabs/cli) project. -For renderers that support hot reloading add this to your main function before you launch your app to start the hot reloading server: +Add this to the top of your main function on any renderer that supports hot reloading to start the hot reloading server: ```rust fn main(){ @@ -79,7 +79,7 @@ fn main(){ } ``` -The dev server watches on the `src` and `examples` folders in the crate directory by default. To watch on custom paths pass the paths into the hot relaod macro: +By default the dev server watches on the `src` and `examples` folders in the root crate directory. To watch on custom paths pass the paths into the hot relaod macro: ```rust fn main(){ @@ -108,7 +108,7 @@ fn main(){ } ``` -## Implementing hot reloading for a custom renderer +## Implementing Hot Reloading for a Custom Renderer To add hot reloading support to your custom renderer you can use the connect function. This will connect to the dev server you just need to provide a way to transfer `Template`s to the `VirtualDom`. Once you implement this your users can use the hot_reload_init function just like any other render. From 4e180bd19ec9b82682ee1eaa9a00fdcef161a467 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 12 Jan 2023 11:00:16 -0600 Subject: [PATCH 13/44] remmove comment --- packages/tui/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index a8cd295d3..8950a7245 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -275,7 +275,6 @@ fn render_vdom( // if we have a new template, replace the old one if let Some(template) = new_templete { - // println!("reloading template"); vdom.replace_template(template); } From 97b9f422e15826f5e1b538274fad06786b01f31b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 12 Jan 2023 11:10:16 -0600 Subject: [PATCH 14/44] log path errors --- packages/hot-reload/src/lib.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index ef87d4d21..8b482c2ce 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -67,9 +67,22 @@ pub fn init( let mut watcher = RecommendedWatcher::new(tx, notify::Config::default()).unwrap(); for path in listening_paths { - if let Ok(path) = PathBuf::from_str(path) { - let examples_path = crate_dir.join(path); - let _ = watcher.watch(&examples_path, RecursiveMode::Recursive); + match PathBuf::from_str(path) { + Ok(path) => { + let full_path = crate_dir.join(path); + if let Err(err) = watcher.watch(&full_path, RecursiveMode::Recursive) { + if log { + println!( + "hot reloading failed to start watching {full_path:?}:\n{err:?}", + ); + } + } + } + Err(err) => { + if log { + println!("hot reloading failed to create path:\n{:?}", err); + } + } } } From f71ab650e86f32cc8839dd289f045d0a03d17b1b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 12 Jan 2023 11:41:46 -0600 Subject: [PATCH 15/44] don't include inteprocess in wasm builds --- packages/dioxus/Cargo.toml | 2 ++ packages/dioxus/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index 0360b207f..afe0ea0c3 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -17,6 +17,8 @@ dioxus-html = { path = "../html", version = "^0.3.0", optional = true } dioxus-core-macro = { path = "../core-macro", version = "^0.3.0", optional = true } dioxus-hooks = { path = "../hooks", version = "^0.3.0", optional = true } dioxus-rsx = { path = "../rsx", version = "0.0.2", optional = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] dioxus-hot-reload = { path = "../hot-reload", version = "0.1.0", optional = true } [features] diff --git a/packages/dioxus/src/lib.rs b/packages/dioxus/src/lib.rs index 4a7d41ea6..5d1371f27 100644 --- a/packages/dioxus/src/lib.rs +++ b/packages/dioxus/src/lib.rs @@ -32,6 +32,6 @@ pub mod prelude { #[cfg(feature = "html")] pub use dioxus_elements::{prelude::*, GlobalAttributes, SvgAttributes}; - #[cfg(feature = "hot-reload")] + #[cfg(all(not(target_arch = "wasm32"), feature = "hot-reload"))] pub use dioxus_hot_reload::{self, hot_reload_init}; } From fc28050da629e59aabd9ae0d52e37b10a21c7768 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 12 Jan 2023 12:09:43 -0600 Subject: [PATCH 16/44] pass clippy --- packages/hot-reload/src/lib.rs | 40 ++++++++++++++++------------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 8b482c2ce..8c40aa2a9 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -32,28 +32,26 @@ pub fn init( let file_map = file_map.clone(); let channels = channels.clone(); move || { - for connection in local_socket_stream.incoming() { - if let Ok(mut connection) = connection { - // send any templates than have changed before the socket connected - let templates: Vec<_> = { - file_map - .lock() - .unwrap() - .map - .values() - .filter_map(|(_, template_slot)| *template_slot) - .collect() - }; - for template in templates { - if !send_template(template, &mut connection) { - continue; - } - } - channels.lock().unwrap().push(connection); - if log { - println!("Connected to hot reloading πŸš€"); + for mut connection in local_socket_stream.incoming().flatten() { + // send any templates than have changed before the socket connected + let templates: Vec<_> = { + file_map + .lock() + .unwrap() + .map + .values() + .filter_map(|(_, template_slot)| *template_slot) + .collect() + }; + for template in templates { + if !send_template(template, &mut connection) { + continue; } } + channels.lock().unwrap().push(connection); + if log { + println!("Connected to hot reloading πŸš€"); + } } } }); @@ -102,7 +100,7 @@ pub fn init( match file_map .lock() .unwrap() - .update_rsx(&path, crate_dir.as_path()) + .update_rsx(path, crate_dir.as_path()) { UpdateResult::UpdatedRsx(msgs) => { for msg in msgs { From 3c229b38bc0f42fa81f2c17b22b6d06c54ac9342 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 12 Jan 2023 18:51:08 -0600 Subject: [PATCH 17/44] make shutdown message more clear --- packages/hot-reload/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 8c40aa2a9..9543911b2 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -118,7 +118,7 @@ pub fn init( UpdateResult::NeedsRebuild => { if log { println!( - "Rebuild needed... shutting down hot reloading" + "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes." ); } return; From b3d119b8150d862d22aeb1149f7f43780746fd8e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 13 Jan 2023 16:13:07 -0600 Subject: [PATCH 18/44] Provide option to do full rebuilds when nessisary --- packages/desktop/src/desktop_context.rs | 4 +- packages/desktop/src/lib.rs | 17 +- packages/hot-reload/Cargo.toml | 3 + packages/hot-reload/src/lib.rs | 251 +++++++++++++++++------- packages/liveview/src/pool.rs | 13 +- packages/tui/src/lib.rs | 24 ++- 6 files changed, 217 insertions(+), 95 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 720223e6c..baa658b28 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -8,8 +8,8 @@ use crate::events::IpcMessage; use crate::Config; use crate::WebviewHandler; use dioxus_core::ScopeState; -use dioxus_core::Template; use dioxus_core::VirtualDom; +use dioxus_hot_reload::HotReloadMsg; use serde_json::Value; use wry::application::event_loop::EventLoopProxy; use wry::application::event_loop::EventLoopWindowTarget; @@ -263,7 +263,7 @@ pub enum EventData { Ipc(IpcMessage), - TemplateUpdated(Template<'static>), + HotReloadEvent(HotReloadMsg), NewWindow, diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 0ec1f922c..9e1bf79db 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -112,7 +112,7 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) let proxy = proxy.clone(); dioxus_hot_reload::connect(move |template| { let _ = proxy.send_event(UserWindowEvent( - EventData::TemplateUpdated(template), + EventData::HotReloadEvent(template), unsafe { WindowId::dummy() }, )); }); @@ -177,13 +177,18 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) } Event::UserEvent(event) => match event.0 { - EventData::TemplateUpdated(template) => { - for webview in webviews.values_mut() { - webview.dom.replace_template(template); + EventData::HotReloadEvent(msg) => match msg { + dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { + for webview in webviews.values_mut() { + webview.dom.replace_template(template); - poll_vdom(webview); + poll_vdom(webview); + } } - } + dioxus_hot_reload::HotReloadMsg::Shutdown => { + *control_flow = ControlFlow::Exit; + } + }, EventData::CloseWindow => { webviews.remove(&event.1); diff --git a/packages/hot-reload/Cargo.toml b/packages/hot-reload/Cargo.toml index d6088a109..7dda13a5d 100644 --- a/packages/hot-reload/Cargo.toml +++ b/packages/hot-reload/Cargo.toml @@ -14,3 +14,6 @@ interprocess = { version = "1.2.1" } notify = "5.0.0" chrono = "0.4.23" serde_json = "1.0.91" +serde = { version = "1", features = ["derive"] } +execute = "0.2.11" +once_cell = "1.17.0" \ No newline at end of file diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 9543911b2..face5edc1 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -15,42 +15,141 @@ use notify::{RecommendedWatcher, RecursiveMode, Watcher}; #[cfg(debug_assertions)] pub use dioxus_html::HtmlCtx; +use serde::{Deserialize, Serialize}; -/// Initialize the hot reloading listener on the given path -pub fn init( +/// A message the hot reloading server sends to the client +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +pub enum HotReloadMsg { + /// A template has been updated + #[serde(borrow = "'static")] + UpdateTemplate(Template<'static>), + /// The program needs to be recompiled, and the client should shut down + Shutdown, +} + +#[derive(Debug, Clone, Copy)] +pub struct Config { root_path: &'static str, listening_paths: &'static [&'static str], + excluded_paths: &'static [&'static str], log: bool, -) { + rebuild_with: Option<&'static str>, + phantom: std::marker::PhantomData, +} + +impl Default for Config { + fn default() -> Self { + Self { + root_path: "", + listening_paths: &[""], + excluded_paths: &["./target"], + log: true, + rebuild_with: None, + phantom: std::marker::PhantomData, + } + } +} + +impl Config { + pub const fn new() -> Self { + Self { + root_path: "", + listening_paths: &[""], + excluded_paths: &["./target"], + log: true, + rebuild_with: None, + phantom: std::marker::PhantomData, + } + } +} + +impl Config { + pub const fn root(self, path: &'static str) -> Self { + Self { + root_path: path, + ..self + } + } + + pub const fn listening_paths(self, paths: &'static [&'static str]) -> Self { + Self { + listening_paths: paths, + ..self + } + } + + pub const fn log(self, log: bool) -> Self { + Self { log, ..self } + } + + pub const fn rebuild_with(self, rebuild_with: &'static str) -> Self { + Self { + rebuild_with: Some(rebuild_with), + ..self + } + } + + pub const fn excluded_paths(self, paths: &'static [&'static str]) -> Self { + Self { + excluded_paths: paths, + ..self + } + } +} + +/// Initialize the hot reloading listener +pub fn init(cfg: Config) { + let Config { + root_path, + listening_paths, + log, + rebuild_with, + excluded_paths, + phantom: _, + } = cfg; + if let Ok(crate_dir) = PathBuf::from_str(root_path) { let temp_file = std::env::temp_dir().join("@dioxusin"); let channels = Arc::new(Mutex::new(Vec::new())); let file_map = Arc::new(Mutex::new(FileMap::::new(crate_dir.clone()))); if let Ok(local_socket_stream) = LocalSocketListener::bind(temp_file.as_path()) { + let aborted = Arc::new(Mutex::new(false)); + // listen for connections std::thread::spawn({ let file_map = file_map.clone(); let channels = channels.clone(); + let aborted = aborted.clone(); + let _ = local_socket_stream.set_nonblocking(true); move || { - for mut connection in local_socket_stream.incoming().flatten() { - // send any templates than have changed before the socket connected - let templates: Vec<_> = { - file_map - .lock() - .unwrap() - .map - .values() - .filter_map(|(_, template_slot)| *template_slot) - .collect() - }; - for template in templates { - if !send_template(template, &mut connection) { - continue; + loop { + if let Ok(mut connection) = local_socket_stream.accept() { + // send any templates than have changed before the socket connected + let templates: Vec<_> = { + file_map + .lock() + .unwrap() + .map + .values() + .filter_map(|(_, template_slot)| *template_slot) + .collect() + }; + for template in templates { + if !send_msg( + HotReloadMsg::UpdateTemplate(template), + &mut connection, + ) { + continue; + } + } + channels.lock().unwrap().push(connection); + if log { + println!("Connected to hot reloading πŸš€"); } } - channels.lock().unwrap().push(connection); - if log { - println!("Connected to hot reloading πŸš€"); + std::thread::sleep(std::time::Duration::from_millis(10)); + if aborted.lock().unwrap().clone() { + break; } } } @@ -65,37 +164,42 @@ pub fn init( let mut watcher = RecommendedWatcher::new(tx, notify::Config::default()).unwrap(); for path in listening_paths { - match PathBuf::from_str(path) { - Ok(path) => { - let full_path = crate_dir.join(path); - if let Err(err) = watcher.watch(&full_path, RecursiveMode::Recursive) { - if log { - println!( - "hot reloading failed to start watching {full_path:?}:\n{err:?}", - ); - } - } - } - Err(err) => { - if log { - println!("hot reloading failed to create path:\n{:?}", err); - } + let full_path = crate_dir.join(path); + if let Err(err) = watcher.watch(&full_path, RecursiveMode::Recursive) { + if log { + println!( + "hot reloading failed to start watching {full_path:?}:\n{err:?}", + ); } } } + let excluded_paths = excluded_paths + .iter() + .map(|path| crate_dir.join(PathBuf::from(path))) + .collect::>(); + for evt in rx { - // Give time for the change to take effect before reading the file - std::thread::sleep(std::time::Duration::from_millis(100)); if chrono::Local::now().timestamp() > last_update_time { if let Ok(evt) = evt { - let mut channels = channels.lock().unwrap(); - for path in &evt.paths { - // skip non rust files - if path.extension().and_then(|p| p.to_str()) != Some("rs") { - continue; - } + let real_paths = evt + .paths + .iter() + .filter(|path| { + // skip non rust files + path.extension().and_then(|p| p.to_str()) == Some("rs") && + // skip excluded paths + !excluded_paths.iter().any(|p| path.starts_with(p)) + }) + .collect::>(); + // Give time for the change to take effect before reading the file + if !real_paths.is_empty() { + std::thread::sleep(std::time::Duration::from_millis(100)); + } + + let mut channels = channels.lock().unwrap(); + for path in real_paths { // find changes to the rsx in the file match file_map .lock() @@ -107,7 +211,10 @@ pub fn init( let mut i = 0; while i < channels.len() { let channel = &mut channels[i]; - if send_template(msg, channel) { + if send_msg( + HotReloadMsg::UpdateTemplate(msg), + channel, + ) { i += 1; } else { channels.remove(i); @@ -116,11 +223,23 @@ pub fn init( } } UpdateResult::NeedsRebuild => { - if log { - println!( - "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes." - ); + if let Some(rebuild_command) = rebuild_with { + *aborted.lock().unwrap() = true; + if log { + println!("Rebuilding the application..."); + } + execute::shell(rebuild_command).spawn().expect("Failed to rebuild the application. Is cargo installed?"); + for channel in &mut *channels { + send_msg(HotReloadMsg::Shutdown, channel); + } + } else { + if log { + println!( + "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes." + ); + } } + return; } } @@ -134,8 +253,8 @@ pub fn init( } } -fn send_template(template: Template<'static>, channel: &mut impl Write) -> bool { - if let Ok(msg) = serde_json::to_string(&template) { +fn send_msg(msg: HotReloadMsg, channel: &mut impl Write) -> bool { + if let Ok(msg) = serde_json::to_string(&msg) { if channel.write_all(msg.as_bytes()).is_err() { return false; } @@ -149,7 +268,7 @@ fn send_template(template: Template<'static>, channel: &mut impl Write) -> bool } /// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected -pub fn connect(mut f: impl FnMut(Template<'static>) + Send + 'static) { +pub fn connect(mut f: impl FnMut(HotReloadMsg) + Send + 'static) { std::thread::spawn(move || { let temp_file = std::env::temp_dir().join("@dioxusin"); if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) { @@ -158,7 +277,7 @@ pub fn connect(mut f: impl FnMut(Template<'static>) + Send + 'static) { let mut buf = String::new(); match buf_reader.read_line(&mut buf) { Ok(_) => { - let template: Template<'static> = + let template: HotReloadMsg = serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap(); f(template); } @@ -179,33 +298,13 @@ pub fn connect(mut f: impl FnMut(Template<'static>) + Send + 'static) { /// If no paths are passed, it will listen on the src and examples folders. #[macro_export] macro_rules! hot_reload_init { - ($(@ $ctx:ident)? $($t: ident)*) => { + () => { #[cfg(debug_assertions)] - dioxus_hot_reload::init::(core::env!("CARGO_MANIFEST_DIR"), &["src", "examples"], hot_reload_init!(@log: $($t)*)) + dioxus_hot_reload::init(dioxus_hot_reload::Config::new().root(env!("CARGO_MANIFEST_DIR"))); }; - ($(@ $ctx:ident)? $($paths: literal),* $(,)? $($t: ident)*) => { + ($cfg: expr) => { #[cfg(debug_assertions)] - dioxus_hot_reload::init::(core::env!("CARGO_MANIFEST_DIR"), &[$($paths),*], hot_reload_init!(@log: $($t)*)) - }; - - (@log:) => { - false - }; - - (@log: enable logging) => { - true - }; - - (@log: disable logging) => { - false - }; - - (@ctx: $ctx: ident) => { - $ctx - }; - - (@ctx: ) => { - dioxus_hot_reload::HtmlCtx + dioxus_hot_reload::init($cfg.root(env!("CARGO_MANIFEST_DIR"))); }; } diff --git a/packages/liveview/src/pool.rs b/packages/liveview/src/pool.rs index 281ada480..b3ed6bef5 100644 --- a/packages/liveview/src/pool.rs +++ b/packages/liveview/src/pool.rs @@ -157,9 +157,16 @@ where } } - new_template = hot_reload_wait => { - if let Some(new_template) = new_template { - vdom.replace_template(new_template); + msg = hot_reload_wait => { + if let Some(msg) = msg { + match msg{ + dioxus_hot_reload::HotReloadMsg::UpdateTemplate(new_template) => { + vdom.replace_template(new_template); + } + dioxus_hot_reload::HotReloadMsg::Shutdown => { + std::process::exit(0); + }, + } } } } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 8950a7245..6961aa6d6 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -6,6 +6,7 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use dioxus_core::*; +use dioxus_hot_reload::HotReloadMsg; use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, NodeMask, SendAnyMap}; use focus::FocusState; use futures::{ @@ -147,9 +148,9 @@ fn render_vdom( .block_on(async { #[cfg(all(feature = "hot-reload", debug_assertions))] let mut hot_reload_rx = { - let (hot_reload_tx, hot_reload_rx) = unbounded_channel::>(); - dioxus_hot_reload::connect(move |template| { - let _ = hot_reload_tx.send(template); + let (hot_reload_tx, hot_reload_rx) = unbounded_channel::(); + dioxus_hot_reload::connect(move |msg| { + let _ = hot_reload_tx.send(msg); }); hot_reload_rx }; @@ -232,7 +233,7 @@ fn render_vdom( } } - let mut new_templete = None; + let mut hot_reload_msg = None; { let wait = vdom.wait_for_work(); #[cfg(all(feature = "hot-reload", debug_assertions))] @@ -267,15 +268,22 @@ fn render_vdom( register_event(evt); } }, - Some(template) = hot_reload_wait => { - new_templete = Some(template); + Some(msg) = hot_reload_wait => { + hot_reload_msg = Some(msg); } } } // if we have a new template, replace the old one - if let Some(template) = new_templete { - vdom.replace_template(template); + if let Some(msg) = hot_reload_msg { + match msg { + HotReloadMsg::UpdateTemplate(template) => { + vdom.replace_template(template); + } + HotReloadMsg::Shutdown => { + break; + } + } } { From cc983dc09173ef84a4116ec22bf3ced01f5bca3e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 13 Jan 2023 16:17:25 -0600 Subject: [PATCH 19/44] rebuild when the cargo.toml changes --- packages/hot-reload/src/lib.rs | 45 +++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index face5edc1..89d08465f 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -179,6 +179,27 @@ pub fn init(cfg: Config) { .map(|path| crate_dir.join(PathBuf::from(path))) .collect::>(); + let rebuild = || { + if let Some(rebuild_command) = rebuild_with { + *aborted.lock().unwrap() = true; + if log { + println!("Rebuilding the application..."); + } + execute::shell(rebuild_command) + .spawn() + .expect("Failed to rebuild the application. Is cargo installed?"); + for channel in &mut *channels.lock().unwrap() { + send_msg(HotReloadMsg::Shutdown, channel); + } + } else { + if log { + println!( + "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes." + ); + } + } + }; + for evt in rx { if chrono::Local::now().timestamp() > last_update_time { if let Ok(evt) = evt { @@ -187,7 +208,7 @@ pub fn init(cfg: Config) { .iter() .filter(|path| { // skip non rust files - path.extension().and_then(|p| p.to_str()) == Some("rs") && + matches!(path.extension().and_then(|p| p.to_str()), Some("rs" | "toml" | "css" | "html" | "js")) && // skip excluded paths !excluded_paths.iter().any(|p| path.starts_with(p)) }) @@ -200,6 +221,10 @@ pub fn init(cfg: Config) { let mut channels = channels.lock().unwrap(); for path in real_paths { + // if this file type cannot be hot reloaded, rebuild the application + if path.extension().and_then(|p| p.to_str()) != Some("rs") { + rebuild(); + } // find changes to the rsx in the file match file_map .lock() @@ -223,22 +248,8 @@ pub fn init(cfg: Config) { } } UpdateResult::NeedsRebuild => { - if let Some(rebuild_command) = rebuild_with { - *aborted.lock().unwrap() = true; - if log { - println!("Rebuilding the application..."); - } - execute::shell(rebuild_command).spawn().expect("Failed to rebuild the application. Is cargo installed?"); - for channel in &mut *channels { - send_msg(HotReloadMsg::Shutdown, channel); - } - } else { - if log { - println!( - "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes." - ); - } - } + drop(channels); + rebuild(); return; } From d642e53a2bdedb1e6408a5abc6efa6ea6a36de57 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 13 Jan 2023 16:25:03 -0600 Subject: [PATCH 20/44] intigrate with .gitignore --- packages/hot-reload/Cargo.toml | 3 ++- packages/hot-reload/src/lib.rs | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/hot-reload/Cargo.toml b/packages/hot-reload/Cargo.toml index 7dda13a5d..034e819fc 100644 --- a/packages/hot-reload/Cargo.toml +++ b/packages/hot-reload/Cargo.toml @@ -16,4 +16,5 @@ chrono = "0.4.23" serde_json = "1.0.91" serde = { version = "1", features = ["derive"] } execute = "0.2.11" -once_cell = "1.17.0" \ No newline at end of file +once_cell = "1.17.0" +gitignore = "1.0.7" \ No newline at end of file diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 89d08465f..e6bebde27 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -157,6 +157,10 @@ pub fn init(cfg: Config) { // watch for changes std::thread::spawn(move || { + // try to find the gitingore file + let gitignore_file_path = crate_dir.join(".gitignore"); + let gitignore_file = gitignore::File::new(&gitignore_file_path.as_path()); + let mut last_update_time = chrono::Local::now().timestamp(); let (tx, rx) = std::sync::mpsc::channel(); @@ -210,7 +214,13 @@ pub fn init(cfg: Config) { // skip non rust files matches!(path.extension().and_then(|p| p.to_str()), Some("rs" | "toml" | "css" | "html" | "js")) && // skip excluded paths - !excluded_paths.iter().any(|p| path.starts_with(p)) + !excluded_paths.iter().any(|p| path.starts_with(p)) && match &gitignore_file{ + Ok(file) => match file.is_excluded(path){ + Ok(excluded) => !excluded, + Err(_) => true, + }, + Err(_) => true, + } }) .collect::>(); From 22f86ed464d2f9612f52da2f9e0387b5154209ed Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 13 Jan 2023 16:57:27 -0600 Subject: [PATCH 21/44] update docs --- .../src/en/getting_started/hot_reload.md | 2 ++ packages/hot-reload/Cargo.toml | 6 ++++ packages/hot-reload/README.md | 32 ++++++++++++------- packages/hot-reload/src/lib.rs | 30 +++++++++-------- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/docs/guide/src/en/getting_started/hot_reload.md b/docs/guide/src/en/getting_started/hot_reload.md index 9700752f4..599ce57da 100644 --- a/docs/guide/src/en/getting_started/hot_reload.md +++ b/docs/guide/src/en/getting_started/hot_reload.md @@ -24,6 +24,8 @@ dioxus serve --hot-reload For desktop, LiveView, and tui, you can place the hot reload macro at the top of your main function to enable hot reloading. Hot reloading is automatically enabled on debug builds. +For more information about hot reloading on native platforms and configuration options see the [dioxus-hot-reload](https://crates.io/crates/dioxus-hot-reload) crate. + ## Setup Add the following to your main function: diff --git a/packages/hot-reload/Cargo.toml b/packages/hot-reload/Cargo.toml index 034e819fc..7f1759f03 100644 --- a/packages/hot-reload/Cargo.toml +++ b/packages/hot-reload/Cargo.toml @@ -2,6 +2,12 @@ name = "dioxus-hot-reload" version = "0.1.0" edition = "2021" +license = "MIT/Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +description = "Hot reloading utilites for Dioxus" +documentation = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react", "hot-reloading", "watch"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/packages/hot-reload/README.md b/packages/hot-reload/README.md index 4e8c7d880..3ae091179 100644 --- a/packages/hot-reload/README.md +++ b/packages/hot-reload/README.md @@ -79,31 +79,32 @@ fn main(){ } ``` -By default the dev server watches on the `src` and `examples` folders in the root crate directory. To watch on custom paths pass the paths into the hot relaod macro: +By default the dev server watches on the root of the crate the macro is called in and ignores changes in the `/target` directory and any directories set in the `.gitignore` file in the root directory. To watch on custom paths pass call the `with_paths` function on the config builder: ```rust fn main(){ - hot_reload_init!("src", "examples", "assets"); + hot_reload_init!(Config::new().with_paths(&["src", "examples", "assets"])); // launch your application } ``` -By default the hot reloading server will output some logs in the console, to disable these logs pass the `disable logging` flag into the macro: +By default the hot reloading server will output some logs in the console, to disable these logs call the `with_logging` function on the config builder: ```rust fn main(){ - hot_reload_init!("src", "examples", "assets", disable logging); + hot_reload_init!(Config::new().with_logging(false)); // launch your application } ``` -If you are using a namespace other than html, you can implement the [HotReloadingContext](https://docs.rs/dioxus-rsx/latest/dioxus_rsx/trait.HotReloadingContext.html) trait to provide a mapping between the rust names of your elements/attributes and the resultsing strings. +If you are using a namespace other than html, you can implement the [HotReloadingContext](https://docs.rs/dioxus-rsx/latest/dioxus_rsx/trait.HotReloadingContext.html) trait to provide a mapping between the rust names of your elements/attributes and the resulting strings. -You can then provide the Context to the macro to make hot reloading work with your custom namespace: +You can then provide the Context to the builder to make hot reloading work with your custom namespace: ```rust fn main(){ - hot_reload_init!(@MyNamespace /*more configeration*/); + // Note: Use default instead of new if you are using a custom namespace + hot_reload_init!(Config::::default()); // launch your application } ``` @@ -118,15 +119,22 @@ async fn launch(app: Component) { // ... let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); - dioxus_hot_reload::connect(move |template| { - let _ = tx.send(template); + dioxus_hot_reload::connect(move |msg| { + let _ = tx.send(msg); }); loop { tokio::select! { - Some(template) = rx.recv() => { - // update the template in the virtual dom - vdom.replace_template(template); + Some(msg) = rx.recv() => { + match msg{ + HotReloadMsg::Shutdown => { + // ... shutdown the application + } + HotReloadMsg::UpdateTemplate(template) => { + // update the template in the virtual dom + vdom.replace_template(template); + } + } } _ = vdom.wait_for_work() => { // ... diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index e6bebde27..fc57b0e56 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -64,6 +64,7 @@ impl Config { } impl Config { + /// Set the root path of the project (where the Cargo.toml file is). This is automatically set by the [`hot_reload_init`] macro. pub const fn root(self, path: &'static str) -> Self { Self { root_path: path, @@ -71,24 +72,30 @@ impl Config { } } - pub const fn listening_paths(self, paths: &'static [&'static str]) -> Self { - Self { - listening_paths: paths, - ..self - } - } - - pub const fn log(self, log: bool) -> Self { + /// Set whether to enable logs + pub const fn with_logging(self, log: bool) -> Self { Self { log, ..self } } - pub const fn rebuild_with(self, rebuild_with: &'static str) -> Self { + /// Set the command to run to rebuild the project + /// + /// For example to restart the application after a change is made, you could use `cargo run` + pub const fn with_rebuild_handler(self, rebuild_with: &'static str) -> Self { Self { rebuild_with: Some(rebuild_with), ..self } } + /// Set the paths to listen for changes in to trigger hot reloading. If this is a directory it will listen for changes in all files in that directory recursively. + pub const fn with_paths(self, paths: &'static [&'static str]) -> Self { + Self { + listening_paths: paths, + ..self + } + } + + /// Sets paths to ignore changes on. This will override any paths set in the [`Config::with_paths`] method in the case of conflicts. pub const fn excluded_paths(self, paths: &'static [&'static str]) -> Self { Self { excluded_paths: paths, @@ -313,10 +320,7 @@ pub fn connect(mut f: impl FnMut(HotReloadMsg) + Send + 'static) { }); } -/// Start the hot reloading server -/// -/// Pass any number of paths to listen for changes on relative to the crate root as strings. -/// If no paths are passed, it will listen on the src and examples folders. +/// Start the hot reloading server with the current directory as the root #[macro_export] macro_rules! hot_reload_init { () => { From 31e21aaa79df57fd9457a2e34e4ee2e45785caf4 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 13 Jan 2023 17:15:06 -0600 Subject: [PATCH 22/44] change rebuild_handler to rebuild_command --- packages/hot-reload/README.md | 9 +++++++++ packages/hot-reload/src/lib.rs | 8 +++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/hot-reload/README.md b/packages/hot-reload/README.md index 3ae091179..126c42103 100644 --- a/packages/hot-reload/README.md +++ b/packages/hot-reload/README.md @@ -97,6 +97,15 @@ fn main(){ } ``` +To rebuild the application when the logic changes, you can use the `with_rebuild_command` function on the config builder. This command will be called when hot reloading fails to quickly update the rsx: + +```rust +fn main(){ + hot_reload_init!(Config::new().with_rebuild_command("cargo run")); + // launch your application +} +``` + If you are using a namespace other than html, you can implement the [HotReloadingContext](https://docs.rs/dioxus-rsx/latest/dioxus_rsx/trait.HotReloadingContext.html) trait to provide a mapping between the rust names of your elements/attributes and the resulting strings. You can then provide the Context to the builder to make hot reloading work with your custom namespace: diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index fc57b0e56..4e3f56ed7 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -80,7 +80,7 @@ impl Config { /// Set the command to run to rebuild the project /// /// For example to restart the application after a change is made, you could use `cargo run` - pub const fn with_rebuild_handler(self, rebuild_with: &'static str) -> Self { + pub const fn with_rebuild_command(self, rebuild_with: &'static str) -> Self { Self { rebuild_with: Some(rebuild_with), ..self @@ -198,7 +198,8 @@ pub fn init(cfg: Config) { } execute::shell(rebuild_command) .spawn() - .expect("Failed to rebuild the application. Is cargo installed?"); + .expect("Failed to spawn the rebuild command"); + for channel in &mut *channels.lock().unwrap() { send_msg(HotReloadMsg::Shutdown, channel); } @@ -233,11 +234,12 @@ pub fn init(cfg: Config) { // Give time for the change to take effect before reading the file if !real_paths.is_empty() { - std::thread::sleep(std::time::Duration::from_millis(100)); + std::thread::sleep(std::time::Duration::from_millis(10)); } let mut channels = channels.lock().unwrap(); for path in real_paths { + println!("File changed: {:?}", path); // if this file type cannot be hot reloaded, rebuild the application if path.extension().and_then(|p| p.to_str()) != Some("rs") { rebuild(); From 34c8ad884975e9d9d0a85573cf48200282ab1ad5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 13 Jan 2023 17:31:55 -0600 Subject: [PATCH 23/44] fix gitignore performance issues --- packages/hot-reload/Cargo.toml | 2 +- packages/hot-reload/src/lib.rs | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/hot-reload/Cargo.toml b/packages/hot-reload/Cargo.toml index 7f1759f03..99d5cb00d 100644 --- a/packages/hot-reload/Cargo.toml +++ b/packages/hot-reload/Cargo.toml @@ -23,4 +23,4 @@ serde_json = "1.0.91" serde = { version = "1", features = ["derive"] } execute = "0.2.11" once_cell = "1.17.0" -gitignore = "1.0.7" \ No newline at end of file +ignore = "0.4.19" \ No newline at end of file diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 4e3f56ed7..da443df49 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -166,7 +166,7 @@ pub fn init(cfg: Config) { std::thread::spawn(move || { // try to find the gitingore file let gitignore_file_path = crate_dir.join(".gitignore"); - let gitignore_file = gitignore::File::new(&gitignore_file_path.as_path()); + let (gitignore, _) = ignore::gitignore::Gitignore::new(gitignore_file_path); let mut last_update_time = chrono::Local::now().timestamp(); @@ -220,15 +220,16 @@ pub fn init(cfg: Config) { .iter() .filter(|path| { // skip non rust files - matches!(path.extension().and_then(|p| p.to_str()), Some("rs" | "toml" | "css" | "html" | "js")) && + matches!( + path.extension().and_then(|p| p.to_str()), + Some("rs" | "toml" | "css" | "html" | "js") + )&& // skip excluded paths - !excluded_paths.iter().any(|p| path.starts_with(p)) && match &gitignore_file{ - Ok(file) => match file.is_excluded(path){ - Ok(excluded) => !excluded, - Err(_) => true, - }, - Err(_) => true, - } + !excluded_paths.iter().any(|p| path.starts_with(p)) && + // respect .gitignore + !gitignore + .matched_path_or_any_parents(path, false) + .is_ignore() }) .collect::>(); From 80755e1d557007a8b11ffb97fd18be7fcbb3ba79 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 13 Jan 2023 17:32:31 -0600 Subject: [PATCH 24/44] remove debug log --- packages/hot-reload/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index da443df49..52769f6d3 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -240,7 +240,6 @@ pub fn init(cfg: Config) { let mut channels = channels.lock().unwrap(); for path in real_paths { - println!("File changed: {:?}", path); // if this file type cannot be hot reloaded, rebuild the application if path.extension().and_then(|p| p.to_str()) != Some("rs") { rebuild(); From 0c6750d1771704f7797c0c61e05c6d3d5612696d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 13 Jan 2023 17:50:32 -0600 Subject: [PATCH 25/44] fix dependancies --- packages/desktop/Cargo.toml | 5 ++--- packages/liveview/Cargo.toml | 4 ++-- packages/tui/Cargo.toml | 7 ++----- packages/tui/src/lib.rs | 8 ++++---- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index bb53eec4d..7a59790ba 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -15,7 +15,7 @@ keywords = ["dom", "ui", "gui", "react"] dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] } dioxus-html = { path = "../html", features = ["serialize"], version = "^0.3.0" } dioxus-interpreter-js = { path = "../interpreter", version = "^0.3.0" } -dioxus-hot-reload = { path = "../hot-reload" } +dioxus-hot-reload = { path = "../hot-reload", optional = true } serde = "1.0.136" serde_json = "1.0.79" @@ -34,7 +34,6 @@ webbrowser = "0.8.0" infer = "0.11.0" dunce = "1.0.2" -interprocess = { version = "1.2.1", optional = true } futures-util = "0.3.25" [target.'cfg(target_os = "ios")'.dependencies] @@ -50,7 +49,7 @@ tokio_runtime = ["tokio"] fullscreen = ["wry/fullscreen"] transparent = ["wry/transparent"] tray = ["wry/tray"] -hot-reload = ["interprocess"] +hot-reload = ["dioxus-hot-reload"] [dev-dependencies] dioxus-core-macro = { path = "../core-macro" } diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index f5cd988b3..fa097be5a 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -26,7 +26,7 @@ serde_json = "1.0.91" dioxus-html = { path = "../html", features = ["serialize"], version = "^0.3.0" } dioxus-core = { path = "../core", features = ["serialize"], version = "^0.3.0" } dioxus-interpreter-js = { path = "../interpreter", version = "0.3.0" } -dioxus-hot-reload = { path = "../hot-reload" } +dioxus-hot-reload = { path = "../hot-reload", optional = true } # warp warp = { version = "0.3.3", optional = true } @@ -54,7 +54,7 @@ tower = "0.4.13" [features] default = ["hot-reload"] # actix = ["actix-files", "actix-web", "actix-ws"] -hot-reload = ["interprocess"] +hot-reload = ["dioxus-hot-reload"] [[example]] name = "axum" diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index d6c96cfd3..bbf939d1a 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -18,7 +18,7 @@ dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] } dioxus-html = { path = "../html", version = "^0.3.0" } dioxus-native-core = { path = "../native-core", version = "^0.2.0" } dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" } -dioxus-hot-reload = { path = "../hot-reload" } +dioxus-hot-reload = { path = "../hot-reload", optional = true } tui = "0.17.0" crossterm = "0.23.0" @@ -30,9 +30,6 @@ smallvec = "1.6" rustc-hash = "1.1.0" anymap = "1.0.0-beta.2" futures-channel = "0.3.25" -interprocess = { version = "1.2.1", optional = true } -serde = { version = "1.0.136", optional = true } -serde_json = { version = "1.0.79", optional = true } [dev-dependencies] dioxus = { path = "../dioxus" } @@ -45,4 +42,4 @@ harness = false [features] default = ["hot-reload"] -hot-reload = ["interprocess", "serde", "serde_json"] +hot-reload = ["dioxus-hot-reload"] diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 6961aa6d6..c7f1dcc79 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -6,7 +6,6 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use dioxus_core::*; -use dioxus_hot_reload::HotReloadMsg; use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, NodeMask, SendAnyMap}; use focus::FocusState; use futures::{ @@ -148,7 +147,8 @@ fn render_vdom( .block_on(async { #[cfg(all(feature = "hot-reload", debug_assertions))] let mut hot_reload_rx = { - let (hot_reload_tx, hot_reload_rx) = unbounded_channel::(); + let (hot_reload_tx, hot_reload_rx) = + unbounded_channel::(); dioxus_hot_reload::connect(move |msg| { let _ = hot_reload_tx.send(msg); }); @@ -277,10 +277,10 @@ fn render_vdom( // if we have a new template, replace the old one if let Some(msg) = hot_reload_msg { match msg { - HotReloadMsg::UpdateTemplate(template) => { + dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { vdom.replace_template(template); } - HotReloadMsg::Shutdown => { + dioxus_hot_reload::HotReloadMsg::Shutdown => { break; } } From 63840bcca415c94c4872e0d67039fe2eb25e198b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 13 Jan 2023 17:55:11 -0600 Subject: [PATCH 26/44] pass clippy --- packages/hot-reload/src/lib.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 52769f6d3..7038dffdb 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -155,7 +155,7 @@ pub fn init(cfg: Config) { } } std::thread::sleep(std::time::Duration::from_millis(10)); - if aborted.lock().unwrap().clone() { + if *aborted.lock().unwrap() { break; } } @@ -203,12 +203,10 @@ pub fn init(cfg: Config) { for channel in &mut *channels.lock().unwrap() { send_msg(HotReloadMsg::Shutdown, channel); } - } else { - if log { - println!( - "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes." - ); - } + } else if log { + println!( + "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes." + ); } }; From 893d0be1a6a0b8d47e3c7e568e4f2f81695b3253 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 15 Jan 2023 11:29:51 -0600 Subject: [PATCH 27/44] add option to specify a callback to make using hot-reloading in a CLI easier --- packages/hot-reload/src/lib.rs | 81 ++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 7038dffdb..c6e48082a 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -27,13 +27,12 @@ pub enum HotReloadMsg { Shutdown, } -#[derive(Debug, Clone, Copy)] pub struct Config { root_path: &'static str, listening_paths: &'static [&'static str], excluded_paths: &'static [&'static str], log: bool, - rebuild_with: Option<&'static str>, + rebuild_with: Option bool + Send + 'static>>, phantom: std::marker::PhantomData, } @@ -65,7 +64,7 @@ impl Config { impl Config { /// Set the root path of the project (where the Cargo.toml file is). This is automatically set by the [`hot_reload_init`] macro. - pub const fn root(self, path: &'static str) -> Self { + pub fn root(self, path: &'static str) -> Self { Self { root_path: path, ..self @@ -73,22 +72,37 @@ impl Config { } /// Set whether to enable logs - pub const fn with_logging(self, log: bool) -> Self { + pub fn with_logging(self, log: bool) -> Self { Self { log, ..self } } /// Set the command to run to rebuild the project /// /// For example to restart the application after a change is made, you could use `cargo run` - pub const fn with_rebuild_command(self, rebuild_with: &'static str) -> Self { + pub fn with_rebuild_command(self, rebuild_command: &'static str) -> Self { + self.with_rebuild_callback(move || { + execute::shell(rebuild_command) + .spawn() + .expect("Failed to spawn the rebuild command"); + true + }) + } + + /// Set a callback to run to when the project needs to be rebuilt and returns if the server should shut down + /// + /// For example a CLI application could rebuild the application when a change is made + pub fn with_rebuild_callback( + self, + rebuild_callback: impl FnMut() -> bool + Send + 'static, + ) -> Self { Self { - rebuild_with: Some(rebuild_with), + rebuild_with: Some(Box::new(rebuild_callback)), ..self } } /// Set the paths to listen for changes in to trigger hot reloading. If this is a directory it will listen for changes in all files in that directory recursively. - pub const fn with_paths(self, paths: &'static [&'static str]) -> Self { + pub fn with_paths(self, paths: &'static [&'static str]) -> Self { Self { listening_paths: paths, ..self @@ -96,7 +110,7 @@ impl Config { } /// Sets paths to ignore changes on. This will override any paths set in the [`Config::with_paths`] method in the case of conflicts. - pub const fn excluded_paths(self, paths: &'static [&'static str]) -> Self { + pub fn excluded_paths(self, paths: &'static [&'static str]) -> Self { Self { excluded_paths: paths, ..self @@ -110,7 +124,7 @@ pub fn init(cfg: Config) { root_path, listening_paths, log, - rebuild_with, + mut rebuild_with, excluded_paths, phantom: _, } = cfg; @@ -190,23 +204,31 @@ pub fn init(cfg: Config) { .map(|path| crate_dir.join(PathBuf::from(path))) .collect::>(); - let rebuild = || { - if let Some(rebuild_command) = rebuild_with { - *aborted.lock().unwrap() = true; - if log { - println!("Rebuilding the application..."); - } - execute::shell(rebuild_command) - .spawn() - .expect("Failed to spawn the rebuild command"); + let mut rebuild = { + let aborted = aborted.clone(); + let channels = channels.clone(); + move || { + if let Some(rebuild_callback) = &mut rebuild_with { + if log { + println!("Rebuilding the application..."); + } + let shutdown = rebuild_callback(); - for channel in &mut *channels.lock().unwrap() { - send_msg(HotReloadMsg::Shutdown, channel); + if shutdown { + *aborted.lock().unwrap() = true; + } + + for channel in &mut *channels.lock().unwrap() { + send_msg(HotReloadMsg::Shutdown, channel); + } + + return shutdown; + } else if log { + println!( + "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes." + ); } - } else if log { - println!( - "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes." - ); + true } }; @@ -240,7 +262,9 @@ pub fn init(cfg: Config) { for path in real_paths { // if this file type cannot be hot reloaded, rebuild the application if path.extension().and_then(|p| p.to_str()) != Some("rs") { - rebuild(); + if rebuild() { + return; + } } // find changes to the rsx in the file match file_map @@ -266,9 +290,10 @@ pub fn init(cfg: Config) { } UpdateResult::NeedsRebuild => { drop(channels); - rebuild(); - - return; + if rebuild() { + return; + } + break; } } } From 418d8b1c3064302a157ee14ac2aca33fd7d93efb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 15 Jan 2023 11:51:17 -0600 Subject: [PATCH 28/44] clippy --- packages/hot-reload/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index c6e48082a..1f6ba7e21 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -261,10 +261,8 @@ pub fn init(cfg: Config) { let mut channels = channels.lock().unwrap(); for path in real_paths { // if this file type cannot be hot reloaded, rebuild the application - if path.extension().and_then(|p| p.to_str()) != Some("rs") { - if rebuild() { - return; - } + if path.extension().and_then(|p| p.to_str()) != Some("rs") && rebuild() { + return; } // find changes to the rsx in the file match file_map From 60219d3f6556b9d7214112b0102d39f04004f837 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 15 Jan 2023 12:01:12 -0600 Subject: [PATCH 29/44] formatting --- packages/hot-reload/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 1f6ba7e21..739cbfc55 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -261,7 +261,9 @@ pub fn init(cfg: Config) { let mut channels = channels.lock().unwrap(); for path in real_paths { // if this file type cannot be hot reloaded, rebuild the application - if path.extension().and_then(|p| p.to_str()) != Some("rs") && rebuild() { + if path.extension().and_then(|p| p.to_str()) != Some("rs") + && rebuild() + { return; } // find changes to the rsx in the file From 04bf6c3f919966e2f1fc3241caf1a64e0f13af92 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 20 Jan 2023 13:28:49 -0600 Subject: [PATCH 30/44] add fuzzing test to native core --- packages/native-core/tests/fuzzing.rs | 417 ++++++++++++++++++++++++++ 1 file changed, 417 insertions(+) create mode 100644 packages/native-core/tests/fuzzing.rs diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs new file mode 100644 index 000000000..078ee37a8 --- /dev/null +++ b/packages/native-core/tests/fuzzing.rs @@ -0,0 +1,417 @@ +use dioxus::prelude::Props; +use dioxus_core::*; +use std::cell::Cell; + +fn random_ns() -> Option<&'static str> { + let namespace = rand::random::() % 2; + match namespace { + 0 => None, + 1 => Some(Box::leak( + format!("ns{}", rand::random::()).into_boxed_str(), + )), + _ => unreachable!(), + } +} + +fn create_random_attribute(attr_idx: &mut usize) -> TemplateAttribute<'static> { + match rand::random::() % 2 { + 0 => TemplateAttribute::Static { + name: Box::leak(format!("attr{}", rand::random::()).into_boxed_str()), + value: Box::leak(format!("value{}", rand::random::()).into_boxed_str()), + namespace: random_ns(), + }, + 1 => TemplateAttribute::Dynamic { + id: { + let old_idx = *attr_idx; + *attr_idx += 1; + old_idx + }, + }, + _ => unreachable!(), + } +} + +fn create_random_template_node( + dynamic_node_types: &mut Vec, + template_idx: &mut usize, + attr_idx: &mut usize, + depth: usize, +) -> TemplateNode<'static> { + match rand::random::() % 4 { + 0 => { + let attrs = { + let attrs: Vec<_> = (0..(rand::random::() % 10)) + .map(|_| create_random_attribute(attr_idx)) + .collect(); + Box::leak(attrs.into_boxed_slice()) + }; + TemplateNode::Element { + tag: Box::leak(format!("tag{}", rand::random::()).into_boxed_str()), + namespace: random_ns(), + attrs, + children: { + if depth > 4 { + &[] + } else { + let children: Vec<_> = (0..(rand::random::() % 3)) + .map(|_| { + create_random_template_node( + dynamic_node_types, + template_idx, + attr_idx, + depth + 1, + ) + }) + .collect(); + Box::leak(children.into_boxed_slice()) + } + }, + } + } + 1 => TemplateNode::Text { + text: Box::leak(format!("{}", rand::random::()).into_boxed_str()), + }, + 2 => TemplateNode::DynamicText { + id: { + let old_idx = *template_idx; + *template_idx += 1; + dynamic_node_types.push(DynamicNodeType::Text); + old_idx + }, + }, + 3 => TemplateNode::Dynamic { + id: { + let old_idx = *template_idx; + *template_idx += 1; + dynamic_node_types.push(DynamicNodeType::Other); + old_idx + }, + }, + _ => unreachable!(), + } +} + +fn generate_paths( + node: &TemplateNode<'static>, + current_path: &[u8], + node_paths: &mut Vec>, + attr_paths: &mut Vec>, +) { + match node { + TemplateNode::Element { + children, attrs, .. + } => { + for attr in *attrs { + match attr { + TemplateAttribute::Static { .. } => {} + TemplateAttribute::Dynamic { .. } => { + attr_paths.push(current_path.to_vec()); + } + } + } + for (i, child) in children.iter().enumerate() { + let mut current_path = current_path.to_vec(); + current_path.push(i as u8); + generate_paths(child, ¤t_path, node_paths, attr_paths); + } + } + TemplateNode::Text { .. } => {} + TemplateNode::DynamicText { .. } => { + node_paths.push(current_path.to_vec()); + } + TemplateNode::Dynamic { .. } => { + node_paths.push(current_path.to_vec()); + } + } +} + +enum DynamicNodeType { + Text, + Other, +} + +fn create_random_template(name: &'static str) -> (Template<'static>, Vec) { + let mut dynamic_node_type = Vec::new(); + let mut template_idx = 0; + let mut attr_idx = 0; + let roots = (0..(1 + rand::random::() % 5)) + .map(|_| { + create_random_template_node(&mut dynamic_node_type, &mut template_idx, &mut attr_idx, 0) + }) + .collect::>(); + assert!(!roots.is_empty()); + let roots = Box::leak(roots.into_boxed_slice()); + let mut node_paths = Vec::new(); + let mut attr_paths = Vec::new(); + for (i, root) in roots.iter().enumerate() { + generate_paths(root, &[i as u8], &mut node_paths, &mut attr_paths); + } + let node_paths = Box::leak( + node_paths + .into_iter() + .map(|v| &*Box::leak(v.into_boxed_slice())) + .collect::>() + .into_boxed_slice(), + ); + let attr_paths = Box::leak( + attr_paths + .into_iter() + .map(|v| &*Box::leak(v.into_boxed_slice())) + .collect::>() + .into_boxed_slice(), + ); + ( + Template { + name, + roots, + node_paths, + attr_paths, + }, + dynamic_node_type, + ) +} + +fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode { + let range = if depth > 3 { 1 } else { 3 }; + match rand::random::() % range { + 0 => DynamicNode::Placeholder(Default::default()), + 1 => cx.make_node((0..(rand::random::() % 5)).map(|_| VNode { + key: None, + parent: Default::default(), + template: Cell::new(Template { + name: concat!(file!(), ":", line!(), ":", column!(), ":0"), + roots: &[TemplateNode::Dynamic { id: 0 }], + node_paths: &[&[0]], + attr_paths: &[], + }), + root_ids: Default::default(), + dynamic_nodes: cx.bump().alloc([cx.component( + create_random_element, + DepthProps { depth, root: false }, + "create_random_element", + )]), + dynamic_attrs: &[], + })), + 2 => cx.component( + create_random_element, + DepthProps { depth, root: false }, + "create_random_element", + ), + _ => unreachable!(), + } +} + +fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute { + let value = match rand::random::() % 6 { + 0 => AttributeValue::Text(Box::leak( + format!("{}", rand::random::()).into_boxed_str(), + )), + 1 => AttributeValue::Float(rand::random()), + 2 => AttributeValue::Int(rand::random()), + 3 => AttributeValue::Bool(rand::random()), + 4 => cx.any_value(rand::random::()), + 5 => AttributeValue::None, + // Listener(RefCell>>), + _ => unreachable!(), + }; + Attribute { + name: Box::leak(format!("attr{}", rand::random::()).into_boxed_str()), + value, + namespace: random_ns(), + mounted_element: Default::default(), + volatile: rand::random(), + } +} + +static mut TEMPLATE_COUNT: usize = 0; + +#[derive(PartialEq, Props)] +struct DepthProps { + depth: usize, + root: bool, +} + +fn create_random_element(cx: Scope) -> Element { + cx.needs_update(); + let range = if cx.props.root { 2 } else { 3 }; + let node = match rand::random::() % range { + 0 | 1 => { + let (template, dynamic_node_types) = create_random_template(Box::leak( + format!( + "{}{}", + concat!(file!(), ":", line!(), ":", column!(), ":"), + { + unsafe { + let old = TEMPLATE_COUNT; + TEMPLATE_COUNT += 1; + old + } + } + ) + .into_boxed_str(), + )); + println!("{template:#?}"); + let node = VNode { + key: None, + parent: None, + template: Cell::new(template), + root_ids: Default::default(), + dynamic_nodes: { + let dynamic_nodes: Vec<_> = dynamic_node_types + .iter() + .map(|ty| match ty { + DynamicNodeType::Text => DynamicNode::Text(VText { + value: Box::leak( + format!("{}", rand::random::()).into_boxed_str(), + ), + id: Default::default(), + }), + DynamicNodeType::Other => { + create_random_dynamic_node(cx, cx.props.depth + 1) + } + }) + .collect(); + cx.bump().alloc(dynamic_nodes) + }, + dynamic_attrs: cx.bump().alloc( + (0..template.attr_paths.len()) + .map(|_| create_random_dynamic_attr(cx)) + .collect::>(), + ), + }; + Some(node) + } + _ => None, + }; + println!("{node:#?}"); + node +} + +use dioxus::prelude::*; +use dioxus_native_core::{ + node_ref::{AttributeMask, NodeView}, + real_dom::RealDom, + state::{ParentDepState, State}, + NodeMask, SendAnyMap, +}; +use dioxus_native_core_macro::{sorted_str_slice, State}; +use std::sync::{Arc, Mutex}; +use tokio::time::sleep; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct BlablaState {} + +/// Font style are inherited by default if not specified otherwise by some of the supported attributes. +impl ParentDepState for BlablaState { + type Ctx = (); + type DepState = (Self,); + + const NODE_MASK: NodeMask = + NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",]))); + + fn reduce<'a>( + &mut self, + _node: NodeView, + _parent: Option<(&'a Self,)>, + _ctx: &Self::Ctx, + ) -> bool { + false + } +} + +#[derive(Clone, State, Default, Debug)] +pub struct NodeState { + #[parent_dep_state(blabla)] + blabla: BlablaState, +} + +mod dioxus_elements { + macro_rules! builder_constructors { + ( + $( + $(#[$attr:meta])* + $name:ident { + $( + $(#[$attr_method:meta])* + $fil:ident: $vil:ident, + )* + }; + )* + ) => { + $( + #[allow(non_camel_case_types)] + $(#[$attr])* + pub struct $name; + + impl $name { + pub const TAG_NAME: &'static str = stringify!($name); + pub const NAME_SPACE: Option<&'static str> = None; + + $( + pub const $fil: (&'static str, Option<&'static str>, bool) = (stringify!($fil), None, false); + )* + } + + impl GlobalAttributes for $name {} + )* + } + } + + pub trait GlobalAttributes {} + + pub trait SvgAttributes {} + + builder_constructors! { + blabla { + + }; + } +} + +// test for panics when creating random nodes and templates +#[test] +fn create() { + for _ in 0..100 { + let mut vdom = VirtualDom::new_with_props( + create_random_element, + DepthProps { + depth: 0, + root: true, + }, + ); + let mutations = vdom.rebuild(); + let mut rdom: RealDom = RealDom::new(); + let (to_update, _diff) = rdom.apply_mutations(mutations); + + let ctx = SendAnyMap::new(); + rdom.update_state(to_update, ctx); + } +} + +// test for panics when diffing random nodes +// This test will change the template every render which is not very realistic, but it helps stress the system +#[test] +fn diff() { + for _ in 0..10 { + let mut vdom = VirtualDom::new_with_props( + create_random_element, + DepthProps { + depth: 0, + root: true, + }, + ); + let mutations = vdom.rebuild(); + let mut rdom: RealDom = RealDom::new(); + let (to_update, _diff) = rdom.apply_mutations(mutations); + + let ctx = SendAnyMap::new(); + rdom.update_state(to_update, ctx); + for _ in 0..10 { + let mutations = vdom.render_immediate(); + let (to_update, _diff) = rdom.apply_mutations(mutations); + + let ctx = SendAnyMap::new(); + rdom.update_state(to_update, ctx); + } + } +} From 278cac6551ad5fc0b151ca86d0e47dc88023022f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 20 Jan 2023 13:42:50 -0600 Subject: [PATCH 31/44] pass clippy --- packages/native-core/tests/fuzzing.rs | 68 ++++----------------------- 1 file changed, 8 insertions(+), 60 deletions(-) diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs index 078ee37a8..d35a98288 100644 --- a/packages/native-core/tests/fuzzing.rs +++ b/packages/native-core/tests/fuzzing.rs @@ -1,5 +1,12 @@ use dioxus::prelude::Props; use dioxus_core::*; +use dioxus_native_core::{ + node_ref::{AttributeMask, NodeView}, + real_dom::RealDom, + state::{ParentDepState, State}, + NodeMask, SendAnyMap, +}; +use dioxus_native_core_macro::{sorted_str_slice, State}; use std::cell::Cell; fn random_ns() -> Option<&'static str> { @@ -287,17 +294,6 @@ fn create_random_element(cx: Scope) -> Element { node } -use dioxus::prelude::*; -use dioxus_native_core::{ - node_ref::{AttributeMask, NodeView}, - real_dom::RealDom, - state::{ParentDepState, State}, - NodeMask, SendAnyMap, -}; -use dioxus_native_core_macro::{sorted_str_slice, State}; -use std::sync::{Arc, Mutex}; -use tokio::time::sleep; - #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct BlablaState {} @@ -309,12 +305,7 @@ impl ParentDepState for BlablaState { const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",]))); - fn reduce<'a>( - &mut self, - _node: NodeView, - _parent: Option<(&'a Self,)>, - _ctx: &Self::Ctx, - ) -> bool { + fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool { false } } @@ -325,49 +316,6 @@ pub struct NodeState { blabla: BlablaState, } -mod dioxus_elements { - macro_rules! builder_constructors { - ( - $( - $(#[$attr:meta])* - $name:ident { - $( - $(#[$attr_method:meta])* - $fil:ident: $vil:ident, - )* - }; - )* - ) => { - $( - #[allow(non_camel_case_types)] - $(#[$attr])* - pub struct $name; - - impl $name { - pub const TAG_NAME: &'static str = stringify!($name); - pub const NAME_SPACE: Option<&'static str> = None; - - $( - pub const $fil: (&'static str, Option<&'static str>, bool) = (stringify!($fil), None, false); - )* - } - - impl GlobalAttributes for $name {} - )* - } - } - - pub trait GlobalAttributes {} - - pub trait SvgAttributes {} - - builder_constructors! { - blabla { - - }; - } -} - // test for panics when creating random nodes and templates #[test] fn create() { From d2e8fbdadee7fd9393079bcb564f5c9acaeca771 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 21 Jan 2023 17:38:15 -0600 Subject: [PATCH 32/44] update custom renderer docs --- docs/guide/src/en/custom_renderer/index.md | 360 +++++++++++++-------- 1 file changed, 225 insertions(+), 135 deletions(-) diff --git a/docs/guide/src/en/custom_renderer/index.md b/docs/guide/src/en/custom_renderer/index.md index 17c2cca07..7c2e89eac 100644 --- a/docs/guide/src/en/custom_renderer/index.md +++ b/docs/guide/src/en/custom_renderer/index.md @@ -2,7 +2,7 @@ Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer. -Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing `DomEdits` and sending `UserEvents`. +Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing `Mutations` and sending `UserEvents`. ## The specifics: @@ -46,114 +46,179 @@ enum Mutation { } ``` -The Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the "push_root" method pushes a new "real" DOM node onto the stack and "append_child" and "replace_with" both remove nodes from the stack. +The Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the [LoadTemplate](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate), [CreatePlaceholder](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.CreatePlaceholder), and [CreateTextNode](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.CreateTextNode) mutations pushes a new "real" DOM node onto the stack and [AppendChildren](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.AppendChildren), [InsertAfter](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.InsertAfter), [InsertBefore](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.InsertBefore), [ReplacePlaceholder](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.ReplacePlaceholder), and [ReplaceWith](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.ReplaceWith) all remove nodes from the stack. +## Node storage + +Dioxus saves and loads elements with IDs. Inside the VirtualDOM, this is just tracked as as a u64. + +Whenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when id is used in a mutation. Dioxus reclaims the IDs of elements when removed. To stay in sync with Dioxus you can use a sparse Vec (Vec>) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when an id does not exist. ### An Example For the sake of understanding, let's consider this example – a very simple UI declaration: ```rust -rsx!( h1 {"count {x}"} ) +rsx!( h1 {"count: {x}"} ) ``` -To get things started, Dioxus must first navigate to the container of this h1 tag. To "navigate" here, the internal diffing algorithm generates the DomEdit `PushRoot` where the ID of the root is the container. +#### Building Templates -When the renderer receives this instruction, it pushes the actual Node onto its own stack. The real renderer's stack will look like this: +The above rsx will create a template that contains one static h1 tag and a placeholder for a dynamic text node. The template contains the static parts of the UI, and ids for the dynamic parts along with the paths to access them. + +The template will look something like this: + +```rust +Template { + // Some id that is unique for the entire project + name: "main.rs:1:1:0", + // The root nodes of the template + roots: &[ + TemplateNode::Element { + tag: "h1", + namespace: None, + attrs: &[], + children: &[ + TemplateNode::DynamicText { + id: 0 + }, + ], + } + ], + // the path to each of the dynamic nodes + node_paths: &[ + // the path to dynamic node with a id of 0 + &[ + // on the first root node + 0, + // the first child of the root node + 0, + ] + ], + // the path to each of the dynamic attributes + attr_paths: &'a [&'a [u8]], +} +``` +> For more detailed docs about the struture of templates see the [Template api docs](https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html) + +This template will be sent to the renderer in the [list of templates](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Mutations.html#structfield.templates) supplied with the mutations the first time it is used. Any time the renderer encounters a [LoadTemplate](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate) mutation after this, it should clone the template and store it in the given id. + +For dynamic nodes and dynamic text nodes, a placeholder node should be created and inserted into the UI so that the node can be navigated to later. + +In HTML renderers, this template could look like: + +```html +

""

+``` + +#### Applying Mutations + +After the renderer has created all of the new templates, it can begin to process the mutations. + +When the renderer starts, it should contain the Root node on the stack and store the Root node with an id of 0. The Root node is the top-level node of the UI. In HTML, this is the `
` element. + +```rust +instructions: [] +stack: [ + RootNode, +] +nodes: [ + RootNode, +] +``` + +The first mutation is a `LoadTemplate` mutation. This tells the renderer to load a root from the template with the given id. The renderer will then push the root node of the template onto the stack and store it with an id for later. In this case, the root node is an h1 element. ```rust instructions: [ - PushRoot(Container) + LoadTemplate { + // the id of the template + name: "main.rs:1:1:0", + // the index of the root node in the template + index: 0, + // the id to store + id: ElementId(1), + } ] stack: [ - ContainerNode, + RootNode, +

""

, +] +nodes: [ + RootNode, +

""

, ] ``` -Next, Dioxus will encounter the h1 node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the DomEdit `CreateElement`. When the renderer receives this instruction, it will create an unmounted node and push it into its own stack: +Next, Dioxus will create the dynamic text node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the Mutation `HydrateText`. When the renderer receives this instruction, it will navigate to the placeholder text node in the template and replace it with the new text. ```rust instructions: [ - PushRoot(Container), - CreateElement(h1), + LoadTemplate { + name: "main.rs:1:1:0", + index: 0, + id: ElementId(1), + }, + HydrateText { + // the id to store the text node + id: ElementId(2), + // the text to set + text: "count: 0", + } ] stack: [ - ContainerNode, - h1, + RootNode, +

"count: 0"

, +] +nodes: [ + RootNode, +

"count: 0"

, + "count: 0", ] ``` -Next, Dioxus sees the text node, and generates the `CreateTextNode` DomEdit: -```rust -instructions: [ - PushRoot(Container), - CreateElement(h1), - CreateTextNode("hello world") -] -stack: [ - ContainerNode, - h1, - "hello world" -] -``` -Remember, the text node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the text node to the h1 element. It depends on the situation, but in this case, we use `AppendChildren`. This pops the text node off the stack, leaving the h1 element as the next element in line. + +Remember, the h1 node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the h1 node to the Root. It depends on the situation, but in this case, we use `AppendChildren`. This pops the text node off the stack, leaving the Root element as the next element on the stack. ```rust instructions: [ - PushRoot(Container), - CreateElement(h1), - CreateTextNode("hello world"), - AppendChildren(1) + LoadTemplate { + name: "main.rs:1:1:0", + index: 0, + id: ElementId(1), + }, + HydrateText { + id: ElementId(2), + text: "count: 0", + }, + AppendChildren { + // the id of the parent node + id: ElementId(0), + // the number of nodes to pop off the stack and append + m: 1 + } ] stack: [ - ContainerNode, - h1 + RootNode, ] -``` -We call `AppendChildren` again, popping off the h1 node and attaching it to the parent: -```rust -instructions: [ - PushRoot(Container), - CreateElement(h1), - CreateTextNode("hello world"), - AppendChildren(1), - AppendChildren(1) +nodes: [ + RootNode, +

"count: 0"

, + "count: 0", ] -stack: [ - ContainerNode, -] -``` -Finally, the container is popped since we don't need it anymore. -```rust -instructions: [ - PushRoot(Container), - CreateElement(h1), - CreateTextNode("hello world"), - AppendChildren(1), - AppendChildren(1), - PopRoot -] -stack: [] ``` Over time, our stack looked like this: ```rust -[] -[Container] -[Container, h1] -[Container, h1, "hello world"] -[Container, h1] -[Container] -[] +[Root] +[Root,

""

] +[Root,

"count: 0"

] +[Root] ``` -Notice how our stack is empty once UI has been mounted. Conveniently, this approach completely separates the Virtual DOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits make Dioxus independent of platform specifics. +Conveniently, this approach completely separates the Virtual DOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits make Dioxus independent of platform specifics. Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing. -It's important to note that there _is_ one layer of connectedness between Dioxus and the renderer. Dioxus saves and loads elements (the PushRoot edit) with an ID. Inside the VirtualDOM, this is just tracked as a u64. - -Whenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when PushRoot(ID) is generated. Dioxus reclaims the IDs of elements when removed. To stay in sync with Dioxus you can use a sparse Vec (Vec>) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when an id does not exist. - -This little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs. A set of serialized DomEditss for various demos is available for you to test your custom renderer against. +This little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs. ## Event loop @@ -223,35 +288,17 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent { ## Custom raw elements -If you need to go as far as relying on custom elements for your renderer – you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away (pose no runtime overhead). You can drop in your own elements any time you want, with little hassle. However, you must be absolutely sure your renderer can handle the new type, or it will crash and burn. +If you need to go as far as relying on custom elements/attributes for your renderer – you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away. You can drop in your elements any time you want, with little hassle. However, you must be sure your renderer can handle the new namespace. -These custom elements are defined as unit structs with trait implementations. - -For example, the `div` element is (approximately!) defined as such: - -```rust -struct div; -impl div { - /// Some glorious documentation about the class property. - const TAG_NAME: &'static str = "div"; - const NAME_SPACE: Option<&'static str> = None; - // define the class attribute - pub fn class<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { - cx.attr("class", val, None, false) - } - // more attributes -} -``` - -You've probably noticed that many elements in the `rsx!` macros support on-hover documentation. The approach we take to custom elements means that the unit struct is created immediately where the element is used in the macro. When the macro is expanded, the doc comments still apply to the unit struct, giving tons of in-editor feedback, even inside a proc macro. +For more examples and information on how to create custom namespaces, see the [`dioxus_html` crate](https://github.com/DioxusLabs/dioxus/blob/master/packages/html/README.md#how-to-extend-it). # Native Core -If you are creating a renderer in rust, native-core provides some utilities to implement a renderer. It provides an abstraction over DomEdits and handles the layout for you. +If you are creating a renderer in rust, the [native-core](https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core) crate provides some utilities to implement a renderer. It provides an abstraction over Mutations and Templates and contains helpers that can handle the layout, and text editing for you. -## RealDom +## The RealDom -The `RealDom` is a higher-level abstraction over updating the Dom. It updates with `DomEdits` and provides a way to incrementally update the state of nodes based on what attributes change. +The `RealDom` is a higher-level abstraction over updating the Dom. It updates with `Mutations` and provides a way to incrementally update the state of nodes based on attributes or other states that change. ### Example @@ -269,11 +316,11 @@ cx.render(rsx!{ }) ``` -In this tree, the color depends on the parent's color. The size depends on the children's size, the current text, and the text size. The border depends on only the current node. +In this tree, the color depends on the parent's color. The layout depends on the children's layout, the current text, and the text size. The border depends on only the current node. In the following diagram arrows represent dataflow: -[![](https://mermaid.ink/img/pako:eNqdVNFqgzAU_RXJXizUUZPJmIM-jO0LukdhpCbO0JhIGteW0n9fNK1Oa0brfUnu9VxyzzkXjyCVhIIYZFzu0hwr7X2-JcIzsa3W3wqXuZdKoele22oddfa1Y0Tnfn31muvMfqeCDNq3GmvaNROmaKqZFO1DPTRhP8MOd1fTWYNDvzlmQbBMJZcq9JtjNgY1mLVUhBqQPQeojl3wGCw5PsjqnIe-zXqEL8GZ2Kz0gVMPmoeU3ND4IcuiaLGY2zRouuKncv_qGKv3VodpJe0JVU6QCQ5kgqMyWQVr8hbk4hm1PBcmsuwmnrCVH94rP7xN_ucp8sOB_EPSfz9drYVrkpc_AmH8_yTjJueUc-ntpOJkgt2os9tKjcYlt-DLUiD3UsB2KZCLcwjv3Aq33-g2v0M0xXA0MBy5DUdXi-gcJZriuLmAOSioKjAj5ld8rMsJ0DktaAJicyVYbRKQiJPBVSUx438QpqUCcYb5ls4BrrRcHUTaFizqnWGzR8W5evoFI-bJdw)](https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNqdVNFqgzAU_RXJXizUUZPJmIM-jO0LukdhpCbO0JhIGteW0n9fNK1Oa0brfUnu9VxyzzkXjyCVhIIYZFzu0hwr7X2-JcIzsa3W3wqXuZdKoele22oddfa1Y0Tnfn31muvMfqeCDNq3GmvaNROmaKqZFO1DPTRhP8MOd1fTWYNDvzlmQbBMJZcq9JtjNgY1mLVUhBqQPQeojl3wGCw5PsjqnIe-zXqEL8GZ2Kz0gVMPmoeU3ND4IcuiaLGY2zRouuKncv_qGKv3VodpJe0JVU6QCQ5kgqMyWQVr8hbk4hm1PBcmsuwmnrCVH94rP7xN_ucp8sOB_EPSfz9drYVrkpc_AmH8_yTjJueUc-ntpOJkgt2os9tKjcYlt-DLUiD3UsB2KZCLcwjv3Aq33-g2v0M0xXA0MBy5DUdXi-gcJZriuLmAOSioKjAj5ld8rMsJ0DktaAJicyVYbRKQiJPBVSUx438QpqUCcYb5ls4BrrRcHUTaFizqnWGzR8W5evoFI-bJdw) +[![](https://mermaid.ink/img/pako:eNqllV1vgjAUhv8K6W4wkQVa2QdLdrHsdlfukmSptEhjoaSWqTH-9xVwONAKst70g5739JzzlO5BJAgFAYi52EQJlsr6fAszS7d1sVhKnCdWJDJFt6peLVs5-9owohK7HFrVcFJ_pxnpmK8VVvRkTJikkWIiaxy1dhP23bUwW1WW5WbPrrqJ4ziR4EJ6dtVN2ls5y1ZztePUcrWZFCvqVEcPPDffvlyS1XoLIQnVgnVvVPR6FU9Zc-6dV453ojjOPbuetRJ57gIeXQR3cez7rjtteZyZQ2j5MqmjqwE0ZW0VKx9RKtgpFewp1aw3sXXFy6TWgiYlv8mfq1scD8ofbBCAfQg8_AMBOAyBxzEIwA4CxgQ99QbQkjnD2KT7_CfxGF8_9WXQEsq5sDZCcjICOXRCri4h6r3NA38Q6Jdi1EOx5w3DGDYYI6MUvJFjM3VoGHUeGoMd6mBnDmh2E3fo7O4Yhf0x4OkBmIKUyhQzol_GfbkcApXQlIYg0EOC5SoEYXbQ-3ChxHyXRSBQsqBTUOREx_7OsAY3BUGM-VqvUsKUkB_1U6vf05gtweEHTk4_HQ?type=png)](https://mermaid.live/edit#pako:eNqllV1vgjAUhv8K6W4wkQVa2QdLdrHsdlfukmSptEhjoaSWqTH-9xVwONAKst70g5739JzzlO5BJAgFAYi52EQJlsr6fAszS7d1sVhKnCdWJDJFt6peLVs5-9owohK7HFrVcFJ_pxnpmK8VVvRkTJikkWIiaxy1dhP23bUwW1WW5WbPrrqJ4ziR4EJ6dtVN2ls5y1ZztePUcrWZFCvqVEcPPDffvlyS1XoLIQnVgnVvVPR6FU9Zc-6dV453ojjOPbuetRJ57gIeXQR3cez7rjtteZyZQ2j5MqmjqwE0ZW0VKx9RKtgpFewp1aw3sXXFy6TWgiYlv8mfq1scD8ofbBCAfQg8_AMBOAyBxzEIwA4CxgQ99QbQkjnD2KT7_CfxGF8_9WXQEsq5sDZCcjICOXRCri4h6r3NA38Q6Jdi1EOx5w3DGDYYI6MUvJFjM3VoGHUeGoMd6mBnDmh2E3fo7O4Yhf0x4OkBmIKUyhQzol_GfbkcApXQlIYg0EOC5SoEYXbQ-3ChxHyXRSBQsqBTUOREx_7OsAY3BUGM-VqvUsKUkB_1U6vf05gtweEHTk4_HQ) [//]: # "%% mermaid flow chart" [//]: # "flowchart TB" @@ -284,33 +331,42 @@ In the following diagram arrows represent dataflow: [//]: # " direction TB" [//]: # " subgraph div state" [//]: # " direction TB" -[//]: # " state1(state)-->color1(color)" -[//]: # " state1-->border1(border)" +[//]: # " state1(state)---color1(color)" +[//]: # " linkStyle 0 stroke-width:10px;" +[//]: # " state1---border1(border)" +[//]: # " linkStyle 1 stroke-width:10px;" [//]: # " text_width-.->layout_width1(layout width)" [//]: # " linkStyle 2 stroke:#ff5500,stroke-width:4px;" -[//]: # " state1-->layout_width1" +[//]: # " state1---layout_width1" +[//]: # " linkStyle 3 stroke-width:10px;" [//]: # " end" [//]: # " subgraph p state" [//]: # " direction TB" -[//]: # " state2(state)-->color2(color)" +[//]: # " state2(state)---color2(color)" +[//]: # " linkStyle 4 stroke-width:10px;" [//]: # " color1-.->color2" [//]: # " linkStyle 5 stroke:#0000ff,stroke-width:4px;" -[//]: # " state2-->border2(border)" +[//]: # " state2---border2(border)" +[//]: # " linkStyle 6 stroke-width:10px;" [//]: # " text_width-.->layout_width2(layout width)" [//]: # " linkStyle 7 stroke:#ff5500,stroke-width:4px;" -[//]: # " state2-->layout_width2" +[//]: # " state2---layout_width2" +[//]: # " linkStyle 8 stroke-width:10px;" [//]: # " layout_width2-.->layout_width1" [//]: # " linkStyle 9 stroke:#00aa00,stroke-width:4px;" [//]: # " end" [//]: # " subgraph hello world state" [//]: # " direction TB" -[//]: # " state3(state)-->border3(border)" -[//]: # " state3-->color3(color)" +[//]: # " state3(state)---border3(border)" +[//]: # " linkStyle 10 stroke-width:10px;" +[//]: # " state3---color3(color)" +[//]: # " linkStyle 11 stroke-width:10px;" [//]: # " color2-.->color3" [//]: # " linkStyle 12 stroke:#0000ff,stroke-width:4px;" [//]: # " text_width-.->layout_width3(layout width)" [//]: # " linkStyle 13 stroke:#ff5500,stroke-width:4px;" -[//]: # " state3-->layout_width3" +[//]: # " state3---layout_width3" +[//]: # " linkStyle 14 stroke-width:10px;" [//]: # " layout_width3-.->layout_width2" [//]: # " linkStyle 15 stroke:#00aa00,stroke-width:4px;" [//]: # " end" @@ -319,25 +375,29 @@ In the following diagram arrows represent dataflow: To help in building a Dom, native-core provides four traits: State, ChildDepState, ParentDepState, NodeDepState, and a RealDom struct. The ChildDepState, ParentDepState, and NodeDepState provide a way to describe how some information in a node relates to that of its relatives. By providing how to build a single node from its relations, native-core will derive a way to update the state of all nodes for you with ```#[derive(State)]```. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom. ```rust + use dioxus_native_core::node_ref::*; use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; use dioxus_native_core_macro::{sorted_str_slice, State}; #[derive(Default, Copy, Clone)] -struct Size(f32, f32); +struct Size(f64, f64); // Size only depends on the current node and its children, so it implements ChildDepState impl ChildDepState for Size { // Size accepts a font size context - type Ctx = f32; + type Ctx = f64; // Size depends on the Size part of each child - type DepState = Self; + type DepState = (Self,); // Size only cares about the width, height, and text parts of the current node const NODE_MASK: NodeMask = - NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["width", "height"]))).with_text(); + NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!([ + "width", "height" + ]))) + .with_text(); fn reduce<'a>( &mut self, node: NodeView, - children: impl Iterator, + children: impl Iterator, ctx: &Self::Ctx, ) -> bool where @@ -347,28 +407,28 @@ impl ChildDepState for Size { let mut height; if let Some(text) = node.text() { // if the node has text, use the text to size our object - width = text.len() as f32 * ctx; + width = text.len() as f64 * ctx; height = *ctx; } else { // otherwise, the size is the maximum size of the children width = children .by_ref() - .map(|item| item.0) + .map(|(item,)| item.0) .reduce(|accum, item| if accum >= item { accum } else { item }) .unwrap_or(0.0); height = children - .map(|item| item.1) + .map(|(item,)| item.1) .reduce(|accum, item| if accum >= item { accum } else { item }) .unwrap_or(0.0); } // if the node contains a width or height attribute it overrides the other size - for a in node.attributes(){ - match a.name{ - "width" => width = a.value.as_float32().unwrap(), - "height" => height = a.value.as_float32().unwrap(), + for a in node.attributes().into_iter().flatten() { + match &*a.attribute.name { + "width" => width = a.value.as_float().unwrap(), + "height" => height = a.value.as_float().unwrap(), // because Size only depends on the width and height, no other attributes will be passed to the member - _ => panic!() + _ => panic!(), } } // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed @@ -388,17 +448,16 @@ struct TextColor { impl ParentDepState for TextColor { type Ctx = (); // TextColor depends on the TextColor part of the parent - type DepState = Self; + type DepState = (Self,); // TextColor only cares about the color attribute of the current node const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["color"])); - fn reduce( - &mut self, - node: NodeView, - parent: Option<&Self::DepState>, - _ctx: &Self::Ctx, - ) -> bool { + fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool { // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags - let new = match node.attributes().next().map(|attr| attr.name) { + let new = match node + .attributes() + .and_then(|attrs| attrs.next()) + .map(|attr| attr.attribute.name.as_str()) + { // if there is a color tag, translate it Some("red") => TextColor { r: 255, g: 0, b: 0 }, Some("green") => TextColor { r: 0, g: 255, b: 0 }, @@ -406,7 +465,7 @@ impl ParentDepState for TextColor { Some(_) => panic!("unknown color"), // otherwise check if the node has a parent and inherit that color None => match parent { - Some(parent) => *parent, + Some((parent,)) => *parent, None => Self::default(), }, }; @@ -420,15 +479,19 @@ impl ParentDepState for TextColor { #[derive(Debug, Clone, PartialEq, Default)] struct Border(bool); // TextColor only depends on the current node, so it implements NodeDepState -impl NodeDepState<()> for Border { +impl NodeDepState for Border { type Ctx = (); - + type DepState = (); + // Border does not depended on any other member in the current node - const NODE_MASK: NodeMask = - NodeMask::new_with_attrs(AttributeMask::Static(&["border"])); + const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["border"])); fn reduce(&mut self, node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool { // check if the node contians a border attribute - let new = Self(node.attributes().next().map(|a| a.name == "border").is_some()); + let new = Self( + node.attributes() + .and_then(|attrs| attrs.next().map(|a| a.attribute.name == "border")) + .is_some(), + ); // check if the member has changed let changed = new != *self; *self = new; @@ -451,7 +514,7 @@ struct ToyState { } ``` -Now that we have our state, we can put it to use in our dom. Re can update the dom with update_state to update the structure of the dom (adding, removing, and changing properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed. +Now that we have our state, we can put it to use in our dom. We can update the dom with update_state to update the structure of the dom (adding, removing, and changing properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed. ```rust fn main(){ fn app(cx: Scope) -> Element { @@ -470,7 +533,7 @@ fn main(){ let to_update = rdom.apply_mutations(vec![mutations]); let mut ctx = AnyMap::new(); // set the font size to 3.3 - ctx.insert(3.3f32); + ctx.insert(3.3f64); // update the ToyState for nodes in the real_dom tree let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap(); @@ -484,7 +547,7 @@ fn main(){ let mutations = vdom.work_with_deadline(|| false); let to_update = rdom.apply_mutations(mutations); let mut ctx = AnyMap::new(); - ctx.insert(3.3); + ctx.insert(3.3f64); let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap(); // render... @@ -494,7 +557,34 @@ fn main(){ ``` ## Layout -For most platforms, the layout of the Elements will stay the same. The layout_attributes module provides a way to apply HTML attributes to a stretch layout style. + +For most platforms, the layout of the Elements will stay the same. The [layout_attributes](https://docs.rs/dioxus-native-core/latest/dioxus_native_core/layout_attributes/index.html) module provides a way to apply HTML attributes a [Taffy](https://docs.rs/taffy/latest/taffy/index.html) layout style. + +## Text Editing + +To make it easier to implement text editing in rust renderers, `native-core` also contains a renderer-agnostic cursor system. The cursor can handle text editing, selection, and movement with common keyboard shortcuts integrated. + +```rust +let mut cursor = Cursor::default(); +let mut text = String::new(); + +let keyboard_data = dioxus_html::KeyboardData::new( + dioxus_html::input_data::keyboard_types::Key::ArrowRight, + dioxus_html::input_data::keyboard_types::Code::ArrowRight, + dioxus_html::input_data::keyboard_types::Location::Standard, + false, + Modifiers::empty(), +); +// handle keyboard input with a max text length of 10 +cursor.handle_input(&keyboard_data, &mut text, 10); + +// mannually select text between characters 0-5 on the first line (this could be from dragging with a mouse) +cursor.start = Pos::new(0, 0); +cursor.end = Some(Pos::new(5, 0)); + +// delete the selected text and move the cursor to the start of the selection +cursor.delete_selection(&mut text); +``` ## Conclusion -That should be it! You should have nearly all the knowledge required on how to implement your own renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the [community](https://discord.gg/XgGxMSkvUM). +That should be it! You should have nearly all the knowledge required on how to implement your renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the [community](https://discord.gg/XgGxMSkvUM). From ba79d4babdb536950ec10e569637ec8365d273be Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 22 Jan 2023 15:05:32 -0600 Subject: [PATCH 33/44] don't-box-scopestates --- packages/core/src/arena.rs | 2 +- packages/core/src/scope_arena.rs | 6 +++--- packages/core/src/virtual_dom.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 75ef20ee1..25726d05b 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -99,7 +99,7 @@ impl VirtualDom { self.ensure_drop_safety(id); if recursive { - if let Some(root) = self.scopes[id.0].as_ref().try_root_node() { + if let Some(root) = self.scopes[id.0].try_root_node() { if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } { self.drop_scope_inner(node) } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 41656fe9d..1ececa01f 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -26,7 +26,7 @@ impl VirtualDom { let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) }; let id = ScopeId(entry.key()); - entry.insert(Box::new(ScopeState { + entry.insert(ScopeState { parent, id, height, @@ -44,13 +44,13 @@ impl VirtualDom { shared_contexts: Default::default(), borrowed_props: Default::default(), attributes_to_drop: Default::default(), - })) + }) } fn acquire_current_scope_raw(&self) -> Option<*const ScopeState> { let id = self.scope_stack.last().copied()?; let scope = self.scopes.get(id.0)?; - Some(scope.as_ref()) + Some(scope) } pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn { diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 9380bcaaf..a63e39aec 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -177,7 +177,7 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future pub struct VirtualDom { // Maps a template path to a map of byteindexes to templates pub(crate) templates: FxHashMap>>, - pub(crate) scopes: Slab>, + pub(crate) scopes: Slab, pub(crate) dirty_scopes: BTreeSet, pub(crate) scheduler: Rc, @@ -291,7 +291,7 @@ impl VirtualDom { /// /// This is useful for inserting or removing contexts from a scope, or rendering out its root node pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> { - self.scopes.get(id.0).map(|f| f.as_ref()) + self.scopes.get(id.0) } /// Get the single scope at the top of the VirtualDom tree that will always be around From ae028d82f898bc0d8c4085dce790b359d493c92c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 22 Jan 2023 17:16:34 -0600 Subject: [PATCH 34/44] use bump slab --- packages/core/Cargo.toml | 2 + packages/core/src/arena.rs | 10 ++-- packages/core/src/create.rs | 6 +-- packages/core/src/diff.rs | 18 +++---- packages/core/src/scheduler/wait.rs | 6 +-- packages/core/src/scope_arena.rs | 8 +-- packages/core/src/scopes.rs | 83 +++++++++++++++++++++++++++++ packages/core/src/virtual_dom.rs | 22 ++++---- 8 files changed, 120 insertions(+), 35 deletions(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index c1c8d44b7..90d35bb0c 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -37,6 +37,8 @@ log = "0.4.17" # Serialize the Edits for use in Webview/Liveview instances serde = { version = "1", features = ["derive"], optional = true } +bumpslab = "0.1.0" + [dev-dependencies] tokio = { version = "1", features = ["full"] } dioxus = { path = "../dioxus" } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 25726d05b..7fbaf0e12 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -92,21 +92,21 @@ impl VirtualDom { // Note: This will not remove any ids from the arena pub(crate) fn drop_scope(&mut self, id: ScopeId, recursive: bool) { self.dirty_scopes.remove(&DirtyScope { - height: self.scopes[id.0].height, + height: self.scopes[id].height, id, }); self.ensure_drop_safety(id); if recursive { - if let Some(root) = self.scopes[id.0].try_root_node() { + if let Some(root) = self.scopes[id].try_root_node() { if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } { self.drop_scope_inner(node) } } } - let scope = &mut self.scopes[id.0]; + let scope = &mut self.scopes[id]; // Drop all the hooks once the children are dropped // this means we'll drop hooks bottom-up @@ -119,7 +119,7 @@ impl VirtualDom { scope.tasks.remove(task_id); } - self.scopes.remove(id.0); + self.scopes.remove(id); } fn drop_scope_inner(&mut self, node: &VNode) { @@ -140,7 +140,7 @@ impl VirtualDom { /// Descend through the tree, removing any borrowed props and listeners pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) { - let scope = &self.scopes[scope_id.0]; + let scope = &self.scopes[scope_id]; // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we // run the hooks (which hold an &mut Reference) diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 8e434d535..3014a7bfe 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -535,7 +535,7 @@ impl<'b> VirtualDom { } // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense - let boundary = match self.scopes[scope.0].has_context::>() { + let boundary = match self.scopes[scope].has_context::>() { Some(boundary) => boundary, _ => return created, }; @@ -544,7 +544,7 @@ impl<'b> VirtualDom { let new_id = self.next_element(new, parent.template.get().node_paths[idx]); // Now connect everything to the boundary - self.scopes[scope.0].placeholder.set(Some(new_id)); + self.scopes[scope].placeholder.set(Some(new_id)); // This involves breaking off the mutations to this point, and then creating a new placeholder for the boundary // Note that we break off dynamic mutations only - since static mutations aren't rendered immediately @@ -583,7 +583,7 @@ impl<'b> VirtualDom { let new_id = self.next_element(template, template.template.get().node_paths[idx]); // Set the placeholder of the scope - self.scopes[scope.0].placeholder.set(Some(new_id)); + self.scopes[scope].placeholder.set(Some(new_id)); // Since the placeholder is already in the DOM, we don't create any new nodes self.mutations.push(AssignId { diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index dbbec6664..37e9a0428 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -15,7 +15,7 @@ use DynamicNode::*; impl<'b> VirtualDom { pub(super) fn diff_scope(&mut self, scope: ScopeId) { - let scope_state = &mut self.scopes[scope.0]; + let scope_state = &mut self.scopes[scope]; self.scope_stack.push(scope); unsafe { @@ -202,7 +202,7 @@ impl<'b> VirtualDom { right.scope.set(Some(scope_id)); // copy out the box for both - let old = self.scopes[scope_id.0].props.as_ref(); + let old = self.scopes[scope_id].props.as_ref(); let new: Box = right.props.take().unwrap(); let new: Box = unsafe { std::mem::transmute(new) }; @@ -214,14 +214,14 @@ impl<'b> VirtualDom { } // First, move over the props from the old to the new, dropping old props in the process - self.scopes[scope_id.0].props = Some(new); + self.scopes[scope_id].props = Some(new); // Now run the component and diff it self.run_scope(scope_id); self.diff_scope(scope_id); self.dirty_scopes.remove(&DirtyScope { - height: self.scopes[scope_id.0].height, + height: self.scopes[scope_id].height, id: scope_id, }); } @@ -721,7 +721,7 @@ impl<'b> VirtualDom { Component(comp) => { let scope = comp.scope.get().unwrap(); - match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { + match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } { RenderReturn::Ready(node) => self.push_all_real_nodes(node), RenderReturn::Aborted(_node) => todo!(), _ => todo!(), @@ -923,14 +923,14 @@ impl<'b> VirtualDom { .expect("VComponents to always have a scope"); // Remove the component from the dom - match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { + match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } { RenderReturn::Ready(t) => self.remove_node(t, gen_muts), RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts), _ => todo!(), }; // Restore the props back to the vcomponent in case it gets rendered again - let props = self.scopes[scope.0].props.take(); + let props = self.scopes[scope].props.take(); *comp.props.borrow_mut() = unsafe { std::mem::transmute(props) }; // Now drop all the resouces @@ -945,7 +945,7 @@ impl<'b> VirtualDom { Some(Placeholder(t)) => t.id.get().unwrap(), Some(Component(comp)) => { let scope = comp.scope.get().unwrap(); - match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { + match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } { RenderReturn::Ready(t) => self.find_first_element(t), _ => todo!("cannot handle nonstandard nodes"), } @@ -961,7 +961,7 @@ impl<'b> VirtualDom { Some(Placeholder(t)) => t.id.get().unwrap(), Some(Component(comp)) => { let scope = comp.scope.get().unwrap(); - match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { + match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } { RenderReturn::Ready(t) => self.find_last_element(t), _ => todo!("cannot handle nonstandard nodes"), } diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index ac7cbcacc..77bea3a18 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -31,7 +31,7 @@ impl VirtualDom { // If the task completes... if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() { // Remove it from the scope so we dont try to double drop it when the scope dropes - let scope = &self.scopes[task.scope.0]; + let scope = &self.scopes[task.scope]; scope.spawned_tasks.borrow_mut().remove(&id); // Remove it from the scheduler @@ -40,7 +40,7 @@ impl VirtualDom { } pub(crate) fn acquire_suspense_boundary(&self, id: ScopeId) -> Rc { - self.scopes[id.0] + self.scopes[id] .consume_context::>() .unwrap() } @@ -64,7 +64,7 @@ impl VirtualDom { if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) { let fiber = self.acquire_suspense_boundary(leaf.scope_id); - let scope = &self.scopes[scope_id.0]; + let scope = &self.scopes[scope_id]; let arena = scope.current_frame(); let ret = arena.bump().alloc(match new_nodes { diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 1ececa01f..9882e3a94 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -49,7 +49,7 @@ impl VirtualDom { fn acquire_current_scope_raw(&self) -> Option<*const ScopeState> { let id = self.scope_stack.last().copied()?; - let scope = self.scopes.get(id.0)?; + let scope = self.scopes.get(id)?; Some(scope) } @@ -60,9 +60,9 @@ impl VirtualDom { self.ensure_drop_safety(scope_id); let mut new_nodes = unsafe { - self.scopes[scope_id.0].previous_frame().bump_mut().reset(); + self.scopes[scope_id].previous_frame().bump_mut().reset(); - let scope = &self.scopes[scope_id.0]; + let scope = &self.scopes[scope_id]; scope.hook_idx.set(0); @@ -127,7 +127,7 @@ impl VirtualDom { } }; - let scope = &self.scopes[scope_id.0]; + let scope = &self.scopes[scope_id]; // We write on top of the previous frame and then make it the current by pushing the generation forward let frame = scope.previous_frame(); diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 5be209c4c..725c26cf9 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -10,12 +10,15 @@ use crate::{ AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId, }; use bumpalo::{boxed::Box as BumpBox, Bump}; +use bumpslab::{BumpSlab, Slot}; use rustc_hash::{FxHashMap, FxHashSet}; +use slab::{Slab, VacantEntry}; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, fmt::{Arguments, Debug}, future::Future, + ops::{Index, IndexMut}, rc::Rc, sync::Arc, }; @@ -63,6 +66,86 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct ScopeId(pub usize); +/// A thin wrapper around a BumpSlab that uses ids to index into the slab. +pub(crate) struct ScopeSlab { + slab: BumpSlab, + // a slab of slots of stable pointers to the ScopeState in the bump slab + entries: Slab>, +} + +impl Default for ScopeSlab { + fn default() -> Self { + Self { + slab: BumpSlab::new(), + entries: Slab::new(), + } + } +} + +impl ScopeSlab { + pub(crate) fn get(&self, id: ScopeId) -> Option<&ScopeState> { + self.entries.get(id.0).map(|slot| unsafe { &*slot.ptr() }) + } + + pub(crate) fn get_mut(&mut self, id: ScopeId) -> Option<&mut ScopeState> { + self.entries + .get(id.0) + .map(|slot| unsafe { &mut *slot.ptr_mut() }) + } + + pub(crate) fn vacant_entry(&mut self) -> ScopeSlabEntry { + let entry = self.entries.vacant_entry(); + ScopeSlabEntry { + slab: &mut self.slab, + entry, + } + } + + pub(crate) fn remove(&mut self, id: ScopeId) { + self.slab.remove(self.entries.remove(id.0)); + } + + pub(crate) fn contains(&self, id: ScopeId) -> bool { + self.entries.contains(id.0) + } + + pub(crate) fn iter(&self) -> impl Iterator { + self.entries.iter().map(|(_, slot)| unsafe { &*slot.ptr() }) + } +} + +pub(crate) struct ScopeSlabEntry<'a> { + slab: &'a mut BumpSlab, + entry: VacantEntry<'a, Slot<'static, ScopeState>>, +} + +impl<'a> ScopeSlabEntry<'a> { + pub(crate) fn key(&self) -> usize { + self.entry.key() + } + + pub(crate) fn insert(self, scope: ScopeState) -> &'a ScopeState { + let slot = self.slab.push(scope); + // this is safe because the slot is only ever accessed with the lifetime of the borrow of the slab + let slot = unsafe { std::mem::transmute(slot) }; + let entry = self.entry.insert(slot); + unsafe { &*entry.ptr() } + } +} + +impl Index for ScopeSlab { + type Output = ScopeState; + fn index(&self, id: ScopeId) -> &Self::Output { + self.get(id).unwrap() + } +} + +impl IndexMut for ScopeSlab { + fn index_mut(&mut self, id: ScopeId) -> &mut Self::Output { + self.get_mut(id).unwrap() + } +} + /// A component's state separate from its props. /// /// This struct exists to provide a common interface for all scopes without relying on generics. diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a63e39aec..a29671f5d 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -5,7 +5,7 @@ use crate::{ any_props::VProps, arena::{ElementId, ElementRef}, - innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, + innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg, ScopeSlab}, mutations::Mutation, nodes::RenderReturn, nodes::{Template, TemplateId}, @@ -177,7 +177,7 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future pub struct VirtualDom { // Maps a template path to a map of byteindexes to templates pub(crate) templates: FxHashMap>>, - pub(crate) scopes: Slab, + pub(crate) scopes: ScopeSlab, pub(crate) dirty_scopes: BTreeSet, pub(crate) scheduler: Rc, @@ -258,7 +258,7 @@ impl VirtualDom { rx, scheduler: Scheduler::new(tx), templates: Default::default(), - scopes: Slab::default(), + scopes: Default::default(), elements: Default::default(), scope_stack: Vec::new(), dirty_scopes: BTreeSet::new(), @@ -291,14 +291,14 @@ impl VirtualDom { /// /// This is useful for inserting or removing contexts from a scope, or rendering out its root node pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> { - self.scopes.get(id.0) + self.scopes.get(id) } /// Get the single scope at the top of the VirtualDom tree that will always be around /// /// This scope has a ScopeId of 0 and is the root of the tree pub fn base_scope(&self) -> &ScopeState { - self.scopes.get(0).unwrap() + self.scopes.get(ScopeId(0)).unwrap() } /// Build the virtualdom with a global context inserted into the base scope @@ -313,7 +313,7 @@ impl VirtualDom { /// /// Whenever the VirtualDom "works", it will re-render this scope pub fn mark_dirty(&mut self, id: ScopeId) { - if let Some(scope) = self.scopes.get(id.0) { + if let Some(scope) = self.scopes.get(id) { let height = scope.height; self.dirty_scopes.insert(DirtyScope { height, id }); } @@ -324,7 +324,7 @@ impl VirtualDom { /// This does not mean the scope is waiting on its own futures, just that the tree that the scope exists in is /// currently suspended. pub fn is_scope_suspended(&self, id: ScopeId) -> bool { - !self.scopes[id.0] + !self.scopes[id] .consume_context::>() .unwrap() .waiting_on @@ -499,7 +499,7 @@ impl VirtualDom { pub fn replace_template(&mut self, template: Template<'static>) { self.register_template_first_byte_index(template); // iterating a slab is very inefficient, but this is a rare operation that will only happen during development so it's fine - for (_, scope) in &self.scopes { + for scope in self.scopes.iter() { if let Some(RenderReturn::Ready(sync)) = scope.try_root_node() { if sync.template.get().name.rsplit_once(':').unwrap().0 == template.name.rsplit_once(':').unwrap().0 @@ -583,7 +583,7 @@ impl VirtualDom { loop { // first, unload any complete suspense trees for finished_fiber in self.finished_fibers.drain(..) { - let scope = &self.scopes[finished_fiber.0]; + let scope = &self.scopes[finished_fiber]; let context = scope.has_context::>().unwrap(); self.mutations @@ -607,7 +607,7 @@ impl VirtualDom { self.dirty_scopes.remove(&dirty); // If the scope doesn't exist for whatever reason, then we should skip it - if !self.scopes.contains(dirty.id.0) { + if !self.scopes.contains(dirty.id) { continue; } @@ -626,7 +626,7 @@ impl VirtualDom { // If suspended leaves are present, then we should find the boundary for this scope and attach things // No placeholder necessary since this is a diff if !self.collected_leaves.is_empty() { - let mut boundary = self.scopes[dirty.id.0] + let mut boundary = self.scopes[dirty.id] .consume_context::>() .unwrap(); From c5f003e41e44c75d328b105fd8451be3b90449fb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 22 Jan 2023 21:07:54 -0600 Subject: [PATCH 35/44] use git version of bump slab --- packages/core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 90d35bb0c..a5d41e58f 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -37,7 +37,7 @@ log = "0.4.17" # Serialize the Edits for use in Webview/Liveview instances serde = { version = "1", features = ["derive"], optional = true } -bumpslab = "0.1.0" +bumpslab = { git = "https://github.com/jkelleyrtp/bumpslab" } [dev-dependencies] tokio = { version = "1", features = ["full"] } From 00a9ad29a8aef878a7c4b33e4e90f5d801711561 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 22 Jan 2023 21:24:30 -0600 Subject: [PATCH 36/44] fix memory leak --- packages/core/src/scopes.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 725c26cf9..275f93c96 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -73,6 +73,15 @@ pub(crate) struct ScopeSlab { entries: Slab>, } +impl Drop for ScopeSlab { + fn drop(&mut self) { + // Bump slab doesn't drop its contents, so we need to do it manually + for slot in self.entries.drain() { + self.slab.remove(slot); + } + } +} + impl Default for ScopeSlab { fn default() -> Self { Self { From bdb63d18f3a58ef332346b0894df7f5791ebf06b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 23 Jan 2023 08:25:08 -0600 Subject: [PATCH 37/44] try a different branch --- packages/core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index a5d41e58f..04f1c3942 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -37,7 +37,7 @@ log = "0.4.17" # Serialize the Edits for use in Webview/Liveview instances serde = { version = "1", features = ["derive"], optional = true } -bumpslab = { git = "https://github.com/jkelleyrtp/bumpslab" } +bumpslab = { git = "https://github.com/demonthos/bumpslab", branch = "reduce-unsafe"} [dev-dependencies] tokio = { version = "1", features = ["full"] } From 82e2175500432d0ad30aeea6cdd214ffc072e988 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 23 Jan 2023 10:08:11 -0600 Subject: [PATCH 38/44] make entry key return scopeid --- packages/core/src/scope_arena.rs | 2 +- packages/core/src/scopes.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 9882e3a94..548329354 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -24,7 +24,7 @@ impl VirtualDom { let parent = self.acquire_current_scope_raw(); let entry = self.scopes.vacant_entry(); let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) }; - let id = ScopeId(entry.key()); + let id = entry.key(); entry.insert(ScopeState { parent, diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 275f93c96..e6f5a7617 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -129,8 +129,8 @@ pub(crate) struct ScopeSlabEntry<'a> { } impl<'a> ScopeSlabEntry<'a> { - pub(crate) fn key(&self) -> usize { - self.entry.key() + pub(crate) fn key(&self) -> ScopeId { + ScopeId(self.entry.key()) } pub(crate) fn insert(self, scope: ScopeState) -> &'a ScopeState { From a0441f686cabc6664d6001a97e1675a7f4494a25 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 23 Jan 2023 12:48:00 -0600 Subject: [PATCH 39/44] use main bumpslab branch --- packages/core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 04f1c3942..a5d41e58f 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -37,7 +37,7 @@ log = "0.4.17" # Serialize the Edits for use in Webview/Liveview instances serde = { version = "1", features = ["derive"], optional = true } -bumpslab = { git = "https://github.com/demonthos/bumpslab", branch = "reduce-unsafe"} +bumpslab = { git = "https://github.com/jkelleyrtp/bumpslab" } [dev-dependencies] tokio = { version = "1", features = ["full"] } From b5c7d5cfd92da3350cca8bdfd9246d127fe5b5db Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 23 Jan 2023 11:45:18 -0800 Subject: [PATCH 40/44] chore: use 0.2 of bumpslab --- packages/core/Cargo.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index a5d41e58f..36c19aa9c 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -23,7 +23,9 @@ rustc-hash = "1.1.0" # Used in diffing longest-increasing-subsequence = "0.1.0" -futures-util = { version = "0.3", default-features = false, features = ["alloc"]} +futures-util = { version = "0.3", default-features = false, features = [ + "alloc", +] } slab = "0.4" @@ -37,7 +39,7 @@ log = "0.4.17" # Serialize the Edits for use in Webview/Liveview instances serde = { version = "1", features = ["derive"], optional = true } -bumpslab = { git = "https://github.com/jkelleyrtp/bumpslab" } +bumpslab = { version = "0.2.0" } [dev-dependencies] tokio = { version = "1", features = ["full"] } From 51ca23b3f7b9474426576c7a83b461bb17ac64fd Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 25 Jan 2023 20:44:42 -0600 Subject: [PATCH 41/44] fix drag serialization on desktop --- packages/interpreter/src/interpreter.js | 80 +++++++++++++------------ 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 77949a764..06774e474 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -445,6 +445,41 @@ class Interpreter { } } +function get_mouse_data(event) { + const { + altKey, + button, + buttons, + clientX, + clientY, + ctrlKey, + metaKey, + offsetX, + offsetY, + pageX, + pageY, + screenX, + screenY, + shiftKey, + } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + offset_x: offsetX, + offset_y: offsetY, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + }; +} + function serialize_event(event) { switch (event.type) { case "copy": @@ -523,10 +558,6 @@ function serialize_event(event) { values: {}, }; } - case "click": - case "contextmenu": - case "doubleclick": - case "dblclick": case "drag": case "dragend": case "dragenter": @@ -534,7 +565,13 @@ function serialize_event(event) { case "dragleave": case "dragover": case "dragstart": - case "drop": + case "drop": { + return { mouse: get_mouse_data(event) }; + } + case "click": + case "contextmenu": + case "doubleclick": + case "dblclick": case "mousedown": case "mouseenter": case "mouseleave": @@ -542,38 +579,7 @@ function serialize_event(event) { case "mouseout": case "mouseover": case "mouseup": { - const { - altKey, - button, - buttons, - clientX, - clientY, - ctrlKey, - metaKey, - offsetX, - offsetY, - pageX, - pageY, - screenX, - screenY, - shiftKey, - } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - offset_x: offsetX, - offset_y: offsetY, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - }; + return get_mouse_data(event); } case "pointerdown": case "pointermove": From 624f32115be948b38ba13e0deb411fc8ff439337 Mon Sep 17 00:00:00 2001 From: Anatolii Kurotych Date: Sat, 28 Jan 2023 04:07:00 +0200 Subject: [PATCH 42/44] Fix release build (#802) --- packages/hot-reload/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 739cbfc55..1ddd32bb1 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -13,7 +13,6 @@ use dioxus_rsx::{ use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; -#[cfg(debug_assertions)] pub use dioxus_html::HtmlCtx; use serde::{Deserialize, Serialize}; From b6c7d44a27e827c8ee22666f7d0bc7b6e5a8b62d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 27 Jan 2023 20:35:46 -0600 Subject: [PATCH 43/44] fix clippy --- docs/guide/examples/hello_world_liveview.rs | 2 +- examples/calculator.rs | 2 +- examples/callback.rs | 2 +- examples/clock.rs | 2 +- examples/dog_app.rs | 2 +- examples/file_explorer.rs | 2 +- examples/filedragdrop.rs | 2 +- examples/inputs.rs | 6 ++-- examples/rsx_usage.rs | 4 +-- examples/ssr.rs | 2 +- examples/textarea.rs | 2 +- packages/autofmt/src/component.rs | 8 ++--- packages/autofmt/src/element.rs | 8 ++--- packages/autofmt/src/expr.rs | 6 ++-- packages/autofmt/src/lib.rs | 2 +- packages/core-macro/src/inlineprops.rs | 2 +- packages/core-macro/src/props/mod.rs | 29 +++++++++---------- packages/core-macro/tests/ifmt.rs | 6 ++-- packages/core/src/nodes.rs | 14 ++++++--- packages/desktop/src/desktop_context.rs | 5 ++-- packages/desktop/src/lib.rs | 2 +- packages/desktop/src/protocol.rs | 5 ++-- packages/hooks/src/usecoroutine.rs | 4 +-- packages/html/src/render_template.rs | 2 +- packages/liveview/examples/axum.rs | 2 +- packages/native-core-macro/src/lib.rs | 2 +- packages/native-core/src/passes.rs | 16 +++++----- packages/native-core/src/state.rs | 4 +-- packages/native-core/src/tree.rs | 16 +++++----- .../src/utils/persistant_iterator.rs | 4 +-- packages/native-core/tests/miri_native.rs | 7 +---- packages/rsx-rosetta/examples/html.rs | 2 +- packages/rsx/src/ifmt.rs | 2 +- packages/ssr/src/cache.rs | 8 ++--- packages/ssr/src/renderer.rs | 2 +- packages/tui/src/render.rs | 15 ++-------- packages/web/tests/hydrate.rs | 2 +- 37 files changed, 96 insertions(+), 107 deletions(-) diff --git a/docs/guide/examples/hello_world_liveview.rs b/docs/guide/examples/hello_world_liveview.rs index e9170cf62..c88dc2f53 100644 --- a/docs/guide/examples/hello_world_liveview.rs +++ b/docs/guide/examples/hello_world_liveview.rs @@ -39,7 +39,7 @@ async fn main() { }), ); - println!("Listening on http://{}", addr); + println!("Listening on http://{addr}"); axum::Server::bind(&addr.to_string().parse().unwrap()) .serve(app.into_make_service()) diff --git a/examples/calculator.rs b/examples/calculator.rs index bf183ba67..bc7f13459 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -82,7 +82,7 @@ fn app(cx: Scope) -> Element { onclick: move |_| { let temp = calc_val(val.as_str()); if temp > 0.0 { - val.set(format!("-{}", temp)); + val.set(format!("-{temp}")); } else { val.set(format!("{}", temp.abs())); } diff --git a/examples/callback.rs b/examples/callback.rs index bf2e3e3a0..cb300b701 100644 --- a/examples/callback.rs +++ b/examples/callback.rs @@ -13,7 +13,7 @@ fn app(cx: Scope) -> Element { .await .unwrap(); - println!("{:#?}, ", res); + println!("{res:#?}, "); }); cx.render(rsx! { diff --git a/examples/clock.rs b/examples/clock.rs index 6b5c59d7e..505a5e87c 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -18,7 +18,7 @@ fn app(cx: Scope) -> Element { loop { tokio::time::sleep(std::time::Duration::from_millis(100)).await; count += 1; - println!("current: {}", count); + println!("current: {count}"); } }); diff --git a/examples/dog_app.rs b/examples/dog_app.rs index 363d85f4b..107251cd7 100644 --- a/examples/dog_app.rs +++ b/examples/dog_app.rs @@ -52,7 +52,7 @@ struct DogApi { #[inline_props] async fn breed_pic(cx: Scope, breed: String) -> Element { let fut = use_future!(cx, |breed| async move { - reqwest::get(format!("https://dog.ceo/api/breed/{}/images/random", breed)) + reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random")) .await .unwrap() .json::() diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs index 64173ad11..88b37789d 100644 --- a/examples/file_explorer.rs +++ b/examples/file_explorer.rs @@ -89,7 +89,7 @@ impl Files { let paths = match std::fs::read_dir(cur_path) { Ok(e) => e, Err(err) => { - let err = format!("An error occured: {:?}", err); + let err = format!("An error occured: {err:?}"); self.err = Some(err); self.path_stack.pop(); return; diff --git a/examples/filedragdrop.rs b/examples/filedragdrop.rs index 79d784f1b..f5b760493 100644 --- a/examples/filedragdrop.rs +++ b/examples/filedragdrop.rs @@ -3,7 +3,7 @@ use dioxus_desktop::Config; fn main() { let cfg = Config::new().with_file_drop_handler(|_w, e| { - println!("{:?}", e); + println!("{e:?}"); true }); diff --git a/examples/inputs.rs b/examples/inputs.rs index 223e3a7e7..cf848d99e 100644 --- a/examples/inputs.rs +++ b/examples/inputs.rs @@ -42,7 +42,7 @@ fn app(cx: Scope) -> Element { // so the value of our input event will be either huey, dewey, louie, or true/false (because of the checkboxe) // be mindful in grouping inputs together, as they will all be handled by the same event handler oninput: move |evt| { - println!("{:?}", evt); + println!("{evt:?}"); }, div { input { @@ -104,7 +104,7 @@ fn app(cx: Scope) -> Element { name: "pdf", r#type: "checkbox", oninput: move |evt| { - println!("{:?}", evt); + println!("{evt:?}"); }, } label { @@ -121,7 +121,7 @@ fn app(cx: Scope) -> Element { r#type: "{field}", value: "{value}", oninput: move |evt: FormEvent| { - println!("{:?}", evt); + println!("{evt:?}"); }, } label { diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index 23ab19cf9..d5e2fcdb3 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -83,7 +83,7 @@ fn app(cx: Scope) -> Element { div { class: { const WORD: &str = "expressions"; - format_args!("Arguments can be passed in through curly braces for complex {}", WORD) + format_args!("Arguments can be passed in through curly braces for complex {WORD}") } } } @@ -214,7 +214,7 @@ fn app(cx: Scope) -> Element { } fn format_dollars(dollars: u32, cents: u32) -> String { - format!("${}.{:02}", dollars, cents) + format!("${dollars}.{cents:02}") } fn helper<'a>(cx: &'a ScopeState, text: &str) -> Element<'a> { diff --git a/examples/ssr.rs b/examples/ssr.rs index bac3759dd..2bc1657cb 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -27,7 +27,7 @@ fn main() { let mut file = String::new(); let mut renderer = dioxus_ssr::Renderer::default(); renderer.render_to(&mut file, &vdom).unwrap(); - println!("{}", file); + println!("{file}"); } fn app(cx: Scope) -> Element { diff --git a/examples/textarea.rs b/examples/textarea.rs index b27a9c99a..df684aa85 100644 --- a/examples/textarea.rs +++ b/examples/textarea.rs @@ -9,7 +9,7 @@ fn main() { fn app(cx: Scope) -> Element { let model = use_state(cx, || String::from("asd")); - println!("{}", model); + println!("{model}"); cx.render(rsx! { textarea { diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index 38cc7815d..89ebe2229 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -140,7 +140,7 @@ impl Writer<'_> { let mut written = generics.to_token_stream().to_string(); written.retain(|c| !c.is_whitespace()); - write!(self.out, "{}", written)?; + write!(self.out, "{written}")?; } write!(self.out, " {{")?; @@ -165,7 +165,7 @@ impl Writer<'_> { match &field.content { ContentField::ManExpr(exp) => { let out = prettyplease::unparse_expr(exp); - write!(self.out, "{}: {}", name, out)?; + write!(self.out, "{name}: {out}")?; } ContentField::Formatted(s) => { write!( @@ -179,11 +179,11 @@ impl Writer<'_> { let out = prettyplease::unparse_expr(exp); let mut lines = out.split('\n').peekable(); let first = lines.next().unwrap(); - write!(self.out, "{}: {}", name, first)?; + write!(self.out, "{name}: {first}")?; for line in lines { self.out.new_line()?; self.out.indented_tab()?; - write!(self.out, "{}", line)?; + write!(self.out, "{line}")?; } } } diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index a949e69f4..6d2cdc830 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -224,7 +224,7 @@ impl Writer<'_> { } ElementAttr::AttrExpression { name, value } => { let out = prettyplease::unparse_expr(value); - write!(self.out, "{}: {}", name, out)?; + write!(self.out, "{name}: {out}")?; } ElementAttr::CustomAttrText { name, value } => { @@ -250,13 +250,13 @@ impl Writer<'_> { // a one-liner for whatever reason // Does not need a new line if lines.peek().is_none() { - write!(self.out, "{}: {}", name, first)?; + write!(self.out, "{name}: {first}")?; } else { - writeln!(self.out, "{}: {}", name, first)?; + writeln!(self.out, "{name}: {first}")?; while let Some(line) = lines.next() { self.out.indented_tab()?; - write!(self.out, "{}", line)?; + write!(self.out, "{line}")?; if lines.peek().is_none() { write!(self.out, "")?; } else { diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index daf0c36ec..ddc0892f8 100644 --- a/packages/autofmt/src/expr.rs +++ b/packages/autofmt/src/expr.rs @@ -20,7 +20,7 @@ impl Writer<'_> { let start = byte_offset(self.raw_src, start); let end = byte_offset(self.raw_src, end); let row = self.raw_src[start..end].trim(); - write!(self.out, "{}", row)?; + write!(self.out, "{row}")?; return Ok(()); } @@ -56,11 +56,11 @@ impl Writer<'_> { write!(self.out, " ")?; } - write!(self.out, "{}", line)?; + write!(self.out, "{line}")?; } else { let offset = offset as usize; let right = &line[offset..]; - write!(self.out, "{}", right)?; + write!(self.out, "{right}")?; } } diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index f1c888ff4..12bf00585 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -102,7 +102,7 @@ pub fn fmt_file(contents: &str) -> Vec { && matches!(body.roots[0], BodyNode::RawExpr(_) | BodyNode::Text(_)); if formatted.len() <= 80 && !formatted.contains('\n') && !body_is_solo_expr { - formatted = format!(" {} ", formatted); + formatted = format!(" {formatted} "); } end_span = span.end(); diff --git a/packages/core-macro/src/inlineprops.rs b/packages/core-macro/src/inlineprops.rs index e814e32fd..8eaadff32 100644 --- a/packages/core-macro/src/inlineprops.rs +++ b/packages/core-macro/src/inlineprops.rs @@ -95,7 +95,7 @@ impl ToTokens for InlinePropsBody { quote! { #vis #f } }); - let struct_name = Ident::new(&format!("{}Props", ident), Span::call_site()); + let struct_name = Ident::new(&format!("{ident}Props"), Span::call_site()); let field_names = inputs.iter().filter_map(|f| match f { FnArg::Receiver(_) => todo!(), diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 56a3407f7..5f3de9dc9 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -323,7 +323,7 @@ mod field_info { let tokenized_code = TokenStream::from_str(&code.value())?; self.default = Some( syn::parse(tokenized_code.into()) - .map_err(|e| Error::new_spanned(code, format!("{}", e)))?, + .map_err(|e| Error::new_spanned(code, format!("{e}")))?, ); } else { return Err(Error::new_spanned(assign.right, "Expected string")); @@ -332,7 +332,7 @@ mod field_info { } _ => Err(Error::new_spanned( &assign, - format!("Unknown parameter {:?}", name), + format!("Unknown parameter {name:?}"), )), } } @@ -503,11 +503,11 @@ mod struct_info { builder_attr, builder_name: syn::Ident::new(&builder_name, proc_macro2::Span::call_site()), conversion_helper_trait_name: syn::Ident::new( - &format!("{}_Optional", builder_name), + &format!("{builder_name}_Optional"), proc_macro2::Span::call_site(), ), core: syn::Ident::new( - &format!("{}_core", builder_name), + &format!("{builder_name}_core"), proc_macro2::Span::call_site(), ), }) @@ -594,7 +594,6 @@ Finally, call `.build()` to create the instance of `{name}`. None => { let doc = format!( "Builder for [`{name}`] instances.\n\nSee [`{name}::builder()`] for more info.", - name = name ); quote!(#[doc = #doc]) } @@ -709,9 +708,9 @@ Finally, call `.build()` to create the instance of `{name}`. }); let reconstructing = self.included_fields().map(|f| f.name); - let &FieldInfo { - name: ref field_name, - ty: ref field_type, + let FieldInfo { + name: field_name, + ty: field_type, .. } = field; let mut ty_generics: Vec = self @@ -810,7 +809,7 @@ Finally, call `.build()` to create the instance of `{name}`. ), proc_macro2::Span::call_site(), ); - let repeated_fields_error_message = format!("Repeated field {}", field_name); + let repeated_fields_error_message = format!("Repeated field {field_name}"); Ok(quote! { #[allow(dead_code, non_camel_case_types, missing_docs)] @@ -929,7 +928,7 @@ Finally, call `.build()` to create the instance of `{name}`. ), proc_macro2::Span::call_site(), ); - let early_build_error_message = format!("Missing required field {}", field_name); + let early_build_error_message = format!("Missing required field {field_name}"); Ok(quote! { #[doc(hidden)] @@ -1037,7 +1036,7 @@ Finally, call `.build()` to create the instance of `{name}`. // I’d prefer β€œa” or β€œan” to β€œits”, but determining which is grammatically // correct is roughly impossible. let doc = - format!("Finalise the builder and create its [`{}`] instance", name); + format!("Finalise the builder and create its [`{name}`] instance"); quote!(#[doc = #doc]) } } @@ -1132,7 +1131,7 @@ Finally, call `.build()` to create the instance of `{name}`. } _ => Err(Error::new_spanned( &assign, - format!("Unknown parameter {:?}", name), + format!("Unknown parameter {name:?}"), )), } } @@ -1146,7 +1145,7 @@ Finally, call `.build()` to create the instance of `{name}`. } _ => Err(Error::new_spanned( &path, - format!("Unknown parameter {:?}", name), + format!("Unknown parameter {name:?}"), )), } } @@ -1161,7 +1160,7 @@ Finally, call `.build()` to create the instance of `{name}`. let call_func = quote!(#call_func); Error::new_spanned( &call.func, - format!("Illegal builder setting group {}", call_func), + format!("Illegal builder setting group {call_func}"), ) })?; match subsetting_name.as_str() { @@ -1173,7 +1172,7 @@ Finally, call `.build()` to create the instance of `{name}`. } _ => Err(Error::new_spanned( &call.func, - format!("Illegal builder setting group name {}", subsetting_name), + format!("Illegal builder setting group name {subsetting_name}"), )), } } diff --git a/packages/core-macro/tests/ifmt.rs b/packages/core-macro/tests/ifmt.rs index e4fd1754f..3e03d37a9 100644 --- a/packages/core-macro/tests/ifmt.rs +++ b/packages/core-macro/tests/ifmt.rs @@ -8,11 +8,11 @@ fn formatting_compiles() { // escape sequences work assert_eq!( format_args_f!("{x:?} {{}}}}").to_string(), - format!("{:?} {{}}}}", x) + format!("{x:?} {{}}}}") ); assert_eq!( format_args_f!("{{{{}} {x:?}").to_string(), - format!("{{{{}} {:?}", x) + format!("{{{{}} {x:?}") ); // paths in formating works @@ -27,6 +27,6 @@ fn formatting_compiles() { // allows duplicate format args assert_eq!( format_args_f!("{x:?} {x:?}").to_string(), - format!("{:?} {:?}", x, x) + format!("{x:?} {x:?}") ); } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 1ca3d532f..da139e78a 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -757,15 +757,21 @@ impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> { } } -impl<'a> IntoDynNode<'_> for &'a str { - fn into_vnode(self, cx: &ScopeState) -> DynamicNode { - cx.text_node(format_args!("{}", self)) +impl<'a, 'b> IntoDynNode<'b> for &'a str { + fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> { + DynamicNode::Text(VText { + value: bumpalo::collections::String::from_str_in(self, cx.bump()).into_bump_str(), + id: Default::default(), + }) } } impl IntoDynNode<'_> for String { fn into_vnode(self, cx: &ScopeState) -> DynamicNode { - cx.text_node(format_args!("{}", self)) + DynamicNode::Text(VText { + value: cx.bump().alloc(self), + id: Default::default(), + }) } } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 1cebc3a28..e5f928222 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -207,13 +207,12 @@ impl DesktopContext { "method":"eval_result", "params": ( function(){{ - {} + {code} }} )() }}) ); - "#, - code + "# ); if let Err(e) = self.webview.evaluate_script(&script) { diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 824bd81f5..dea69e532 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -326,5 +326,5 @@ fn send_edits(edits: Mutations, webview: &WebView) { let serialized = serde_json::to_string(&edits).unwrap(); // todo: use SSE and binary data to send the edits with lower overhead - _ = webview.evaluate_script(&format!("window.interpreter.handleEdits({})", serialized)); + _ = webview.evaluate_script(&format!("window.interpreter.handleEdits({serialized})")); } diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index 067684cd2..f8c002a6a 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -11,15 +11,14 @@ fn module_loader(root_name: &str) -> String { -"#, - root_name +"# ) } diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index 8c735bd80..2f2442f87 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -120,7 +120,7 @@ mod tests { fn app(cx: Scope, name: String) -> Element { let task = use_coroutine(cx, |mut rx: UnboundedReceiver| async move { while let Some(msg) = rx.next().await { - println!("got message: {}", msg); + println!("got message: {msg}"); } }); @@ -133,7 +133,7 @@ mod tests { async fn view_task(mut rx: UnboundedReceiver) { while let Some(msg) = rx.next().await { - println!("got message: {}", msg); + println!("got message: {msg}"); } } diff --git a/packages/html/src/render_template.rs b/packages/html/src/render_template.rs index e608f6aa1..6811364b9 100644 --- a/packages/html/src/render_template.rs +++ b/packages/html/src/render_template.rs @@ -25,7 +25,7 @@ fn render_template_node(node: &TemplateNode, out: &mut String) -> std::fmt::Resu write!(out, "<{tag}")?; for attr in *attrs { if let TemplateAttribute::Static { name, value, .. } = attr { - write!(out, "{}=\"{}\"", name, value)?; + write!(out, "{name}=\"{value}\"")?; } } for child in *children { diff --git a/packages/liveview/examples/axum.rs b/packages/liveview/examples/axum.rs index 410c0e9a4..df24de4aa 100644 --- a/packages/liveview/examples/axum.rs +++ b/packages/liveview/examples/axum.rs @@ -46,7 +46,7 @@ async fn main() { }), ); - println!("Listening on http://{}", addr); + println!("Listening on http://{addr}"); axum::Server::bind(&addr.to_string().parse().unwrap()) .serve(app.into_make_service()) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 29ee4b055..9da0bee16 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -311,7 +311,7 @@ impl Parse for Dependency { input .parse::() .ok() - .filter(|i: &Ident| format!("{}", i) != "NONE") + .filter(|i: &Ident| i != "NONE") .map(|i| vec![i]) }) .unwrap_or_default(); diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index bb0e26e48..9ff42827a 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -151,10 +151,10 @@ pub trait Pass { } pub trait UpwardPass: Pass { - fn pass<'a>( + fn pass( &self, node: &mut T, - children: &mut dyn Iterator, + children: &mut dyn Iterator, ctx: &SendAnyMap, ) -> PassReturn; } @@ -764,10 +764,10 @@ fn up_pass() { } } impl UpwardPass for AddPass { - fn pass<'a>( + fn pass( &self, node: &mut i32, - children: &mut dyn Iterator, + children: &mut dyn Iterator, _: &SendAnyMap, ) -> PassReturn { *node += children.map(|i| *i).sum::(); @@ -830,10 +830,10 @@ fn dependant_up_pass() { } } impl UpwardPass for AddPass { - fn pass<'a>( + fn pass( &self, node: &mut i32, - children: &mut dyn Iterator, + children: &mut dyn Iterator, _: &SendAnyMap, ) -> PassReturn { *node += children.map(|i| *i).sum::(); @@ -863,10 +863,10 @@ fn dependant_up_pass() { } } impl UpwardPass for SubtractPass { - fn pass<'a>( + fn pass( &self, node: &mut i32, - children: &mut dyn Iterator, + children: &mut dyn Iterator, _: &SendAnyMap, ) -> PassReturn { *node -= children.map(|i| *i).sum::(); diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 44eb81e97..45dc344d5 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -251,7 +251,7 @@ impl ChildDepState for () { impl ParentDepState for () { type Ctx = (); type DepState = (); - fn reduce<'a>(&mut self, _: NodeView<'a>, _: Option<()>, _: &Self::Ctx) -> bool { + fn reduce(&mut self, _: NodeView, _: Option<()>, _: &Self::Ctx) -> bool { false } } @@ -259,7 +259,7 @@ impl ParentDepState for () { impl NodeDepState for () { type DepState = (); type Ctx = (); - fn reduce<'a>(&mut self, _: NodeView<'a>, _sibling: (), _: &Self::Ctx) -> bool { + fn reduce(&mut self, _: NodeView, _sibling: (), _: &Self::Ctx) -> bool { false } } diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 760afe87b..56dfb8f59 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -426,7 +426,7 @@ fn creation() { let child = tree.create_node(0); tree.add_child(parent, child); - println!("Tree: {:#?}", tree); + println!("Tree: {tree:#?}"); assert_eq!(tree.size(), 2); assert_eq!(tree.height(parent), Some(0)); assert_eq!(tree.height(child), Some(1)); @@ -448,7 +448,7 @@ fn insertion() { let after = tree.create_node(3); tree.insert_after(child, after); - println!("Tree: {:#?}", tree); + println!("Tree: {tree:#?}"); assert_eq!(tree.size(), 4); assert_eq!(tree.height(parent), Some(0)); assert_eq!(tree.height(child), Some(1)); @@ -475,7 +475,7 @@ fn deletion() { let after = tree.create_node(3); tree.insert_after(child, after); - println!("Tree: {:#?}", tree); + println!("Tree: {tree:#?}"); assert_eq!(tree.size(), 4); assert_eq!(tree.height(parent), Some(0)); assert_eq!(tree.height(child), Some(1)); @@ -492,7 +492,7 @@ fn deletion() { tree.remove(child); - println!("Tree: {:#?}", tree); + println!("Tree: {tree:#?}"); assert_eq!(tree.size(), 3); assert_eq!(tree.height(parent), Some(0)); assert_eq!(tree.height(before), Some(1)); @@ -507,7 +507,7 @@ fn deletion() { tree.remove(before); - println!("Tree: {:#?}", tree); + println!("Tree: {tree:#?}"); assert_eq!(tree.size(), 2); assert_eq!(tree.height(parent), Some(0)); assert_eq!(tree.height(after), Some(1)); @@ -519,7 +519,7 @@ fn deletion() { tree.remove(after); - println!("Tree: {:#?}", tree); + println!("Tree: {tree:#?}"); assert_eq!(tree.size(), 1); assert_eq!(tree.height(parent), Some(0)); assert_eq!(*tree.get(parent).unwrap(), 0); @@ -562,7 +562,7 @@ fn get_node_children_mut() { for (i, child) in children.enumerate() { assert_eq!(*child, i + 1); } - println!("Parent: {:#?}", parent); + println!("Parent: {parent:#?}"); } #[test] @@ -574,7 +574,7 @@ fn get_many_mut_unchecked() { let all = unsafe { slab.get_many_mut_unchecked([parent, child, grandchild].into_iter()) }.unwrap(); - println!("All: {:#?}", all); + println!("All: {all:#?}"); } #[derive(Debug)] diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 521d449fc..47221c766 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -368,7 +368,7 @@ fn persist_removes() { let mut rdom: RealDom = RealDom::new(); let build = vdom.rebuild(); - println!("{:#?}", build); + println!("{build:#?}"); let _to_update = rdom.apply_mutations(build); // this will end on the node that is removed @@ -400,7 +400,7 @@ fn persist_removes() { vdom.mark_dirty(ScopeId(0)); let update = vdom.render_immediate(); - println!("{:#?}", update); + println!("{update:#?}"); iter1.prune(&update, &rdom); iter2.prune(&update, &rdom); let _to_update = rdom.apply_mutations(update); diff --git a/packages/native-core/tests/miri_native.rs b/packages/native-core/tests/miri_native.rs index 338f86915..8c3695114 100644 --- a/packages/native-core/tests/miri_native.rs +++ b/packages/native-core/tests/miri_native.rs @@ -20,12 +20,7 @@ impl ParentDepState for BlablaState { const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",]))); - fn reduce<'a>( - &mut self, - _node: NodeView, - _parent: Option<(&'a Self,)>, - _ctx: &Self::Ctx, - ) -> bool { + fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool { false } } diff --git a/packages/rsx-rosetta/examples/html.rs b/packages/rsx-rosetta/examples/html.rs index d3aabb86c..ff9af0ed7 100644 --- a/packages/rsx-rosetta/examples/html.rs +++ b/packages/rsx-rosetta/examples/html.rs @@ -20,5 +20,5 @@ fn main() { let out = dioxus_autofmt::write_block_out(body).unwrap(); - println!("{}", out); + println!("{out}"); } diff --git a/packages/rsx/src/ifmt.rs b/packages/rsx/src/ifmt.rs index 13256aa74..c7a3c2e26 100644 --- a/packages/rsx/src/ifmt.rs +++ b/packages/rsx/src/ifmt.rs @@ -193,7 +193,7 @@ pub struct FormattedSegment { impl ToTokens for FormattedSegment { fn to_tokens(&self, tokens: &mut TokenStream) { let (fmt, seg) = (&self.format_args, &self.segment); - let fmt = format!("{{0:{}}}", fmt); + let fmt = format!("{{0:{fmt}}}"); tokens.append_all(quote! { format_args!(#fmt, #seg) }); diff --git a/packages/ssr/src/cache.rs b/packages/ssr/src/cache.rs index 54ff65b26..39bfb8671 100644 --- a/packages/ssr/src/cache.rs +++ b/packages/ssr/src/cache.rs @@ -60,11 +60,11 @@ impl StringCache { .. } => { cur_path.push(root_idx); - write!(chain, "<{}", tag)?; + write!(chain, "<{tag}")?; for attr in *attrs { match attr { TemplateAttribute::Static { name, value, .. } => { - write!(chain, " {}=\"{}\"", name, value)?; + write!(chain, " {name}=\"{value}\"")?; } TemplateAttribute::Dynamic { id: index } => { chain.segments.push(Segment::Attr(*index)) @@ -78,11 +78,11 @@ impl StringCache { for child in *children { Self::recurse(child, cur_path, root_idx, chain)?; } - write!(chain, "", tag)?; + write!(chain, "")?; } cur_path.pop(); } - TemplateNode::Text { text } => write!(chain, "{}", text)?, + TemplateNode::Text { text } => write!(chain, "{text}")?, TemplateNode::Dynamic { id: idx } | TemplateNode::DynamicText { id: idx } => { chain.segments.push(Segment::Node(*idx)) } diff --git a/packages/ssr/src/renderer.rs b/packages/ssr/src/renderer.rs index 05dd7b80b..8c828f01c 100644 --- a/packages/ssr/src/renderer.rs +++ b/packages/ssr/src/renderer.rs @@ -124,7 +124,7 @@ impl Renderer { } }, - Segment::PreRendered(contents) => write!(buf, "{}", contents)?, + Segment::PreRendered(contents) => write!(buf, "{contents}")?, } } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index e7de5eaac..2ff9bf863 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -115,10 +115,7 @@ impl RinkWidget for &TuiNode { [0, 1] => Direction::Down, [0, -1] => Direction::Up, [a, b] => { - panic!( - "draw({:?} {:?} {:?}) {}, {} no cell adjacent", - before, current, after, a, b - ) + panic!("draw({before:?} {current:?} {after:?}) {a}, {b} no cell adjacent") } }; let end_dir = match [after[0] - current[0], after[1] - current[1]] { @@ -127,10 +124,7 @@ impl RinkWidget for &TuiNode { [0, 1] => Direction::Down, [0, -1] => Direction::Up, [a, b] => { - panic!( - "draw({:?} {:?} {:?}) {}, {} no cell adjacent", - before, current, after, a, b - ) + panic!("draw({before:?} {current:?} {after:?}) {a}, {b} no cell adjacent") } }; @@ -151,10 +145,7 @@ impl RinkWidget for &TuiNode { [Direction::Left, Direction::Up] => symbols.bottom_right, [Direction::Left, Direction::Right] => symbols.horizontal, [Direction::Left, Direction::Down] => symbols.top_right, - _ => panic!( - "{:?} {:?} {:?} cannont connect cell to itself", - before, current, after - ), + _ => panic!("{before:?} {current:?} {after:?} cannont connect cell to itself"), } .to_string(); buf.set( diff --git a/packages/web/tests/hydrate.rs b/packages/web/tests/hydrate.rs index 936466a14..c7de26a8c 100644 --- a/packages/web/tests/hydrate.rs +++ b/packages/web/tests/hydrate.rs @@ -58,7 +58,7 @@ fn rehydrates() { .unwrap() .body() .unwrap() - .set_inner_html(&format!("
{}
", out)); + .set_inner_html(&format!("
{out}
")); dioxus_web::launch_cfg(app, Config::new().hydrate(true)); } From 6fde323112d6f6b09760f0d550d0bc3c21d454fc Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 27 Jan 2023 23:27:28 -0800 Subject: [PATCH 44/44] bump core version --- packages/core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 36c19aa9c..9f855e18e 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-core" -version = "0.3.0" +version = "0.3.1" authors = ["Jonathan Kelley"] edition = "2018" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"