diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 08d6cf085..6ecad18b5 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -23,9 +23,6 @@ rustc-hash = "1.1.0" # Used in diffing longest-increasing-subsequence = "0.1.0" -# internall used -log = { version = "0.4" } - futures-util = { version = "0.3", default-features = false } smallvec = "1.6" @@ -34,16 +31,18 @@ slab = "0.4" futures-channel = "0.3.21" +# internally used +log = "0.4" + # used for noderefs once_cell = "1.8" -indexmap = "1.7" # Serialize the Edits for use in Webview/Liveview instances serde = { version = "1", features = ["derive"], optional = true } # todo: I want to get rid of this -backtrace = "0.3" +backtrace = { version = "0.3" } # allows cloing trait objects dyn-clone = "1.0.9" @@ -52,4 +51,4 @@ dyn-clone = "1.0.9" default = [] serialize = ["serde"] debug_vdom = [] -hot-reload = [] \ No newline at end of file +hot-reload = [] diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 417bce426..09637d8a1 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -2,13 +2,13 @@ //! //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust. +use std::{collections::VecDeque, iter::FromIterator, task::Poll}; + use crate::diff::DiffState; use crate::innerlude::*; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures_util::{future::poll_fn, StreamExt}; -use indexmap::IndexSet; use rustc_hash::FxHashSet; -use std::{collections::VecDeque, iter::FromIterator, task::Poll}; /// A virtual node system that progresses user events and diffs UI trees. /// @@ -107,7 +107,8 @@ pub struct VirtualDom { scopes: ScopeArena, pending_messages: VecDeque, - dirty_scopes: IndexSet, + dirty_scopes: Vec, + removed_scopes: FxHashSet, channel: ( UnboundedSender, @@ -238,8 +239,9 @@ impl VirtualDom { root: ElementId(0), scopes, channel, - dirty_scopes: IndexSet::from_iter([ScopeId(0)]), + dirty_scopes: Vec::from_iter([ScopeId(0)]), pending_messages: VecDeque::new(), + removed_scopes: FxHashSet::default(), } } @@ -396,11 +398,18 @@ impl VirtualDom { } } SchedulerMsg::Immediate(s) => { - self.dirty_scopes.insert(s); + self.mark_dirty_scope(s); } SchedulerMsg::DirtyAll => { - for id in self.scopes.scopes.borrow().keys() { - self.dirty_scopes.insert(*id); + let dirty = self + .scopes + .scopes + .borrow() + .keys() + .copied() + .collect::>(); + for id in dirty { + self.mark_dirty_scope(id); } } #[cfg(any(feature = "hot-reload", debug_assertions))] @@ -474,6 +483,7 @@ impl VirtualDom { pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec { let mut committed_mutations = vec![]; self.scopes.template_bump.reset(); + self.removed_scopes.clear(); while !self.dirty_scopes.is_empty() { let scopes = &self.scopes; @@ -492,7 +502,10 @@ impl VirtualDom { }); if let Some(scopeid) = self.dirty_scopes.pop() { - if !ran_scopes.contains(&scopeid) { + if scopes.get_scope(scopeid).is_some() + && !self.removed_scopes.contains(&scopeid) + && !ran_scopes.contains(&scopeid) + { ran_scopes.insert(scopeid); self.scopes.run_scope(scopeid); @@ -501,9 +514,8 @@ impl VirtualDom { let DiffState { mutations, .. } = diff_state; - for scope in &mutations.dirty_scopes { - self.dirty_scopes.remove(scope); - } + self.removed_scopes + .extend(mutations.dirty_scopes.iter().copied()); if !mutations.edits.is_empty() { committed_mutations.push(mutations); @@ -729,6 +741,23 @@ impl VirtualDom { }) .unwrap() } + + fn mark_dirty_scope(&mut self, scope_id: ScopeId) { + let scopes = &self.scopes; + if let Some(scope) = scopes.get_scope(scope_id) { + let height = scope.height; + let id = scope_id.0; + if let Err(index) = self.dirty_scopes.binary_search_by(|new| { + let scope = scopes.get_scope(*new).unwrap(); + let new_height = scope.height; + let new_id = &scope.scope_id(); + height.cmp(&new_height).then(new_id.0.cmp(&id)) + }) { + self.dirty_scopes.insert(index, scope_id); + log::info!("mark_dirty_scope: {:?}", self.dirty_scopes); + } + } + } } /* @@ -736,7 +765,8 @@ Scopes and ScopeArenas are never dropped internally. An app will always occupy as much memory as its biggest form. This means we need to handle all specifics of drop *here*. It's easier -to reason about centralizing all the drop logic in one spot rather than scattered in each module. +to reason about centralizing all the drop +logic in one spot rather than scattered in each module. Broadly speaking, we want to use the remove_nodes method to clean up *everything* This will drop listeners, borrowed props, and hooks for all components. diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index 41c4d2854..a9cfa340d 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../../packages/core", version = "^0.2.1" } futures-channel = "0.3.21" -log = { version = "0.4" } +log = "0.4" [dev-dependencies] diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 2f35d0323..a36197e50 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -20,7 +20,7 @@ dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features js-sys = "0.3.56" wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] } wasm-bindgen-futures = "0.4.29" -log = { version = "0.4.14" } +log = "0.4.14" rustc-hash = "1.1.0" console_error_panic_hook = { version = "0.1.7", optional = true } once_cell = "1.9.0" @@ -28,7 +28,6 @@ anyhow = "1.0.53" gloo-timers = { version = "0.2.3", features = ["futures"] } futures-util = "0.3.19" smallstr = "0.2.0" -serde-wasm-bindgen = "0.4.2" futures-channel = "0.3.21" serde_json = { version = "1.0" } @@ -78,13 +77,14 @@ features = [ ] [features] -default = ["panic_hook"] +default = ["panic_hook", "hydrate"] panic_hook = ["console_error_panic_hook"] hot-reload = ["dioxus/hot-reload"] - +hydrate = [] [dev-dependencies] dioxus = { path = "../dioxus" } wasm-bindgen-test = "0.3.29" dioxus-ssr = { path = "../ssr" } wasm-logger = "0.2.0" +dioxus-web = { path = "." } \ No newline at end of file diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 6e6137fde..98e89ee47 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -12,7 +12,7 @@ use dioxus_html::event_bubbles; use dioxus_interpreter_js::Interpreter; use js_sys::Function; use std::{any::Any, rc::Rc, sync::Arc}; -use wasm_bindgen::{closure::Closure, JsCast}; +use wasm_bindgen::{closure::Closure, JsCast, JsValue}; use web_sys::{Document, Element, Event, HtmlElement}; use crate::Config; @@ -148,11 +148,11 @@ impl WebsysDom { } DomEdit::CreateTextNode { text, root } => { - let text = serde_wasm_bindgen::to_value(text).unwrap(); + let text = JsValue::from_str(text); self.interpreter.CreateTextNode(text, root) } DomEdit::SetText { root, text } => { - let text = serde_wasm_bindgen::to_value(text).unwrap(); + let text = JsValue::from_str(text); self.interpreter.SetText(root, text) } DomEdit::SetAttribute { @@ -161,7 +161,7 @@ impl WebsysDom { value, ns, } => { - let value = serde_wasm_bindgen::to_value(&value).unwrap(); + let value = JsValue::from_str(&value.to_string()); self.interpreter.SetAttribute(root, field, value, ns) } DomEdit::CloneNode { id, new_id } => self.interpreter.CloneNode(id, new_id), diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index b37d29c1f..4c0831ef5 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -67,6 +67,7 @@ mod cfg; mod dom; #[cfg(any(feature = "hot-reload", debug_assertions))] mod hot_reload; +#[cfg(feature = "hydrate")] mod rehydrate; // mod ric_raf; mod util; @@ -164,7 +165,8 @@ where pub async fn run_with_props(root: Component, root_props: T, cfg: Config) { let mut dom = VirtualDom::new_with_props(root, root_props); - if cfg!(feature = "panic_hook") && cfg.default_panic_hook { + #[cfg(feature = "panic_hook")] + if cfg.default_panic_hook { console_error_panic_hook::set_once(); } @@ -194,6 +196,8 @@ pub async fn run_with_props(root: Component, root_props: T // it's a waste to produce edits just to get the vdom loaded let _ = dom.rebuild(); + #[cfg(feature = "hydrate")] + #[allow(unused_variables)] if let Err(err) = websys_dom.rehydrate(&dom) { log::error!( "Rehydration failed {:?}. Rebuild DOM into element from scratch",