From c271fc132e409531d0f4a6708dcf2712a16d8065 Mon Sep 17 00:00:00 2001 From: Adia Robbie Date: Wed, 6 Jul 2022 23:05:31 +0700 Subject: [PATCH 1/2] dioxus-desktop(Update-dep): tauri 0.19.x (#489) * Update(dep): tauri 0.19.x --- packages/desktop/Cargo.toml | 2 +- packages/desktop/src/events.rs | 5 +++-- packages/desktop/src/lib.rs | 7 ------- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index c5beefbdc..850860eda 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -21,7 +21,7 @@ serde = "1.0.136" serde_json = "1.0.79" thiserror = "1.0.30" log = "0.4.14" -wry = { version = "0.16.0" } +wry = { version = "0.19.0" } futures-channel = "0.3.21" tokio = { version = "1.16.1", features = [ "sync", diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index 803767a09..37c491a05 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -6,8 +6,9 @@ use std::sync::Arc; use dioxus_core::{ElementId, EventPriority, UserEvent}; use dioxus_html::event_bubbles; use dioxus_html::on::*; +use serde::{Deserialize, Serialize}; -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Deserialize, Serialize)] pub(crate) struct IpcMessage { method: String, params: serde_json::Value, @@ -33,7 +34,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option { } } -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Deserialize, Serialize)] struct ImEvent { event: String, mounted_dom_id: u64, diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index edf8211a1..f1c7fbab5 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -213,13 +213,6 @@ pub fn launch_with_props( } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow), - - WindowEvent::Resized(_) | WindowEvent::Moved(_) => { - if let Some(view) = desktop.webviews.get_mut(&window_id) { - let _ = view.resize(); - } - } - _ => {} }, From 4a5ae758eec395d823cccbba7b00bd11b4c6a717 Mon Sep 17 00:00:00 2001 From: Demonthos <66571940+Demonthos@users.noreply.github.com> Date: Wed, 6 Jul 2022 23:50:22 -0500 Subject: [PATCH 2/2] Allow Multiple dependencies for states in the RealDom (native core) (#412) * WIP multable deps * WIP: refactor and genralize state macro * add traversable trait * update tui to use elementid to index dom --- docs/reference/src/guide/custom_renderer.md | 85 +- packages/core-macro/src/props/mod.rs | 7 +- packages/html/src/events.rs | 2 +- packages/native-core-macro/src/lib.rs | 843 ++++++++---------- .../tests/called_minimally_on_build.rs | 256 ++++++ .../native-core-macro/tests/change_nodes.rs | 12 +- .../native-core-macro/tests/initial_build.rs | 14 +- .../native-core-macro/tests/update_state.rs | 103 +-- packages/native-core/src/lib.rs | 2 + packages/native-core/src/node_ref.rs | 4 +- packages/native-core/src/real_dom.rs | 478 ++-------- packages/native-core/src/state.rs | 159 ++-- packages/native-core/src/traversable.rs | 100 +++ packages/tui/src/focus.rs | 5 +- packages/tui/src/hooks.rs | 2 +- packages/tui/src/lib.rs | 11 +- packages/tui/src/node.rs | 6 +- packages/tui/src/query.rs | 2 +- packages/tui/src/render.rs | 2 +- 19 files changed, 990 insertions(+), 1103 deletions(-) create mode 100644 packages/native-core-macro/tests/called_minimally_on_build.rs create mode 100644 packages/native-core/src/traversable.rs diff --git a/docs/reference/src/guide/custom_renderer.md b/docs/reference/src/guide/custom_renderer.md index 35187e2e3..61f35c9ba 100644 --- a/docs/reference/src/guide/custom_renderer.md +++ b/docs/reference/src/guide/custom_renderer.md @@ -243,11 +243,11 @@ You've probably noticed that many elements in the `rsx!` macros support on-hover # Native Core -Renderers take a lot of work. If you are creating a renderer in rust, native core provides some utilites to implement a renderer. It provides an abstraction over DomEdits and handles layout for you. +If you are creating a renderer in rust, native core provides some utilites to implement a renderer. It provides an abstraction over DomEdits and handles layout for you. ## RealDom -The `RealDom` is a higher level abstraction over updating the Dom. It updates with `DomEdits` and provides a way to lazily update the state of nodes based on what attributes change. +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. ### Example @@ -267,43 +267,52 @@ cx.render(rsx!{ In this tree the color depends on the parent's color. The size depends on the childrens size, the current text, and a text size. The border depends on only the current node. -```mermaid -flowchart TB - subgraph context - text_width(text width) - end - subgraph div - state1(state)-->color1(color) - state1(state)-->border1(border) - border1-.->text_width - linkStyle 2 stroke:#5555ff,stroke-width:4px; - state1(state)-->layout_width1(layout width) - end - subgraph p - state2(state)-->color2(color) - color2-.->color1(color) - linkStyle 5 stroke:#0000ff,stroke-width:4px; - state2(state)-->border2(border) - border2-.->text_width - linkStyle 7 stroke:#5555ff,stroke-width:4px; - state2(state)-->layout_width2(layout width) - layout_width1-.->layout_width2 - linkStyle 9 stroke:#aaaaff,stroke-width:4px; - end - subgraph hello world - state3(state)-->color3(color) - color3-.->color2(color) - linkStyle 11 stroke:#0000ff,stroke-width:4px; - state3(state)-->border3(border) - border3-.->text_width - linkStyle 13 stroke:#5555ff,stroke-width:4px; - state3(state)-->layout_width3(layout width) - layout_width2-.->layout_width3 - linkStyle 15 stroke:#aaaaff,stroke-width:4px; - end -``` +In the following diagram arrows represent dataflow: -To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState and a RealDom struct. +[![](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) + +[//]: # "%% mermaid flow chart" +[//]: # "flowchart TB" +[//]: # " subgraph context" +[//]: # " text_width(text width)" +[//]: # " end" +[//]: # " subgraph state" +[//]: # " direction TB" +[//]: # " subgraph div state" +[//]: # " direction TB" +[//]: # " state1(state)-->color1(color)" +[//]: # " state1-->border1(border)" +[//]: # " text_width-.->layout_width1(layout width)" +[//]: # " linkStyle 2 stroke:#ff5500,stroke-width:4px;" +[//]: # " state1-->layout_width1" +[//]: # " end" +[//]: # " subgraph p state" +[//]: # " direction TB" +[//]: # " state2(state)-->color2(color)" +[//]: # " color1-.->color2" +[//]: # " linkStyle 5 stroke:#0000ff,stroke-width:4px;" +[//]: # " state2-->border2(border)" +[//]: # " text_width-.->layout_width2(layout width)" +[//]: # " linkStyle 7 stroke:#ff5500,stroke-width:4px;" +[//]: # " state2-->layout_width2" +[//]: # " 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)" +[//]: # " 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" +[//]: # " layout_width3-.->layout_width2" +[//]: # " linkStyle 15 stroke:#00aa00,stroke-width:4px;" +[//]: # " end" +[//]: # " end" + +To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState and a RealDom struct. The ChildDepState, ParentDepState, and NodeDepState provide a way to discribe 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::*; diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 92772df00..1d5c58d1f 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -83,7 +83,7 @@ mod util { } pub fn expr_to_single_string(expr: &syn::Expr) -> Option { - if let syn::Expr::Path(path) = &*expr { + if let syn::Expr::Path(path) = expr { path_to_single_string(&path.path) } else { None @@ -779,13 +779,12 @@ Finally, call `.build()` to create the instance of `{name}`. // NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of // nesting is different so we have to do this little dance. let arg_type = if field.builder_attr.strip_option { - let internal_type = field.type_from_inside_option(false).ok_or_else(|| { + field.type_from_inside_option(false).ok_or_else(|| { Error::new_spanned( &field_type, "can't `strip_option` - field is not `Option<...>`", ) - })?; - internal_type + })? } else { field_type }; diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index d2fa8d3ba..430740f5b 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -937,7 +937,7 @@ pub mod on { feature = "serialize", derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr) )] -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub enum KeyCode { // That key has no keycode, = 0 diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 58fa3e3a9..1c82c8fe4 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -2,11 +2,12 @@ extern crate proc_macro; mod sorted_slice; -use dioxus_native_core::state::MemberId; use proc_macro::TokenStream; -use quote::format_ident; -use quote::{quote, ToTokens, __private::Span}; +use quote::{quote, ToTokens}; use sorted_slice::StrSlice; +use syn::parenthesized; +use syn::parse::ParseBuffer; +use syn::punctuated::Punctuated; use syn::{ self, parse::{Parse, ParseStream, Result}, @@ -51,228 +52,99 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let strct = Struct::new(type_name.clone(), &fields); match StateStruct::parse(&fields, &strct) { Ok(state_strct) => { - let node_dep_state_fields = state_strct + let members: Vec<_> = state_strct .state_members .iter() - .filter(|f| f.dep_kind == DepKind::Node) - .map(|f| f.reduce_self()); - let child_dep_state_fields = state_strct - .state_members - .iter() - .filter(|f| f.dep_kind == DepKind::Child) - .map(|f| f.reduce_self()); - let parent_dep_state_fields = state_strct - .state_members - .iter() - .filter(|f| f.dep_kind == DepKind::Parent) - .map(|f| f.reduce_self()); - - let node_iter = state_strct - .state_members - .iter() - .filter(|m| m.dep_kind == DepKind::Node); - let node_ids = node_iter.clone().map(|m| m.member_id.0); - let node_ids_clone = node_ids.clone(); - let node_types = node_iter.map(|f| &f.mem.ty); - - let child_iter = state_strct - .state_members - .iter() - .filter(|m| m.dep_kind == DepKind::Child); - let child_ids = child_iter.clone().map(|m| m.member_id.0); - let child_ids_clone = child_ids.clone(); - let child_types = child_iter.map(|f| &f.mem.ty); - - let parent_iter = state_strct - .state_members - .iter() - .filter(|m| m.dep_kind == DepKind::Parent); - let parent_ids = parent_iter.clone().map(|m| m.member_id.0); - let parent_ids_clone = parent_ids.clone(); - let parent_types = parent_iter.map(|f| &f.mem.ty); - - let type_name_str = type_name.to_string(); - - let child_states = &state_strct.child_states; - - let member_size = state_strct.state_members.len(); - - let child_state_ty = child_states.iter().map(|m| &m.ty); - let child_state_idents: Vec<_> = child_states.iter().map(|m| &m.ident).collect(); - let sum_const_declarations = child_state_ty.clone().enumerate().map(|(i, ty)| { - let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string()); - let ident_minus = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string()); - if i == 0 { - quote!(const #ident_minus: usize = #member_size + #ty::SIZE - 1; - const #ident: usize = #member_size + #ty::SIZE;) - } else { - let prev_ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string()); - quote!(const #ident_minus: usize = #prev_ident + #ty::SIZE - 1; - const #ident: usize = #prev_ident + #ty::SIZE;) - } - }); - let sum_idents: Vec<_> = std::iter::once(quote!(#member_size)) - .chain((0..child_states.len()).map(|i| { - let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string()); - quote!(#ident) - })) + .map(|m| &m.mem.ident) .collect(); + let member_types = state_strct.state_members.iter().map(|m| &m.mem.ty); + let resolve_members = state_strct + .state_members + .iter() + .map(|m| state_strct.resolve(m)); - let child_state_ranges: Vec<_> = (0..child_state_ty.len()) - .map(|i| { - let current = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string()); - let previous = if i == 0 { - quote!(#member_size) - } else { - let ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string()); - quote!(#ident) - }; - quote!(#previous..=#current) - }) - .collect(); + let child_types = state_strct.child_states.iter().map(|s| &s.ty); + let child_members = state_strct.child_states.iter().map(|s| &s.ident); let gen = quote! { - #( - #sum_const_declarations - )* - impl State for #type_name{ - const SIZE: usize = #member_size #( + #child_state_ty::SIZE)*; - - fn update_node_dep_state<'a>( - &'a mut self, - ty: dioxus_native_core::state::MemberId, - node: &'a dioxus_core::VNode<'a>, + impl State for #type_name { + fn update<'a, T: dioxus_native_core::traversable::Traversable>( + dirty: &[(dioxus_core::ElementId, dioxus_native_core::node_ref::NodeMask)], + state_tree: &'a mut T, vdom: &'a dioxus_core::VirtualDom, ctx: &anymap::AnyMap, - ) -> Option{ - use dioxus_native_core::state::NodeDepState as _; - use dioxus_native_core::state::State as _; - match ty.0{ - #( - #node_ids => #node_dep_state_fields, - )* - #( - #child_state_ranges => { - self.#child_state_idents.update_node_dep_state( - ty - #sum_idents, - node, - vdom, - ctx, - ).map(|mut changed|{ - for id in &mut changed.node_dep{ - *id += #sum_idents; - } - changed - }) - } - )* - _ => panic!("{:?} not in {}", ty, #type_name_str), + ) -> fxhash::FxHashSet{ + #[derive(Eq, PartialEq)] + struct HeightOrdering { + height: u16, + id: dioxus_core::ElementId, } - } - fn update_parent_dep_state<'a>( - &'a mut self, - ty: dioxus_native_core::state::MemberId, - node: &'a dioxus_core::VNode<'a>, - vdom: &'a dioxus_core::VirtualDom, - parent: Option<&Self>, - ctx: &anymap::AnyMap, - ) -> Option{ - use dioxus_native_core::state::ParentDepState as _; - match ty.0{ - #( - #parent_ids => #parent_dep_state_fields, - )* - #( - #child_state_ranges => { - self.#child_state_idents.update_parent_dep_state( - ty - #sum_idents, - node, - vdom, - parent.map(|p| &p.#child_state_idents), - ctx, - ).map(|mut changed|{ - for id in &mut changed.node_dep{ - *id += #sum_idents; - } - for id in &mut changed.parent_dep{ - *id += #sum_idents; - } - changed - }) + impl HeightOrdering { + fn new(height: u16, id: dioxus_core::ElementId) -> Self { + HeightOrdering { + height, + id, } - )* - _ => panic!("{:?} not in {}", ty, #type_name_str), + } } - } - fn update_child_dep_state<'a>( - &'a mut self, - ty: dioxus_native_core::state::MemberId, - node: &'a dioxus_core::VNode<'a>, - vdom: &'a dioxus_core::VirtualDom, - children: &[&Self], - ctx: &anymap::AnyMap, - ) -> Option{ - use dioxus_native_core::state::ChildDepState as _; - match ty.0{ - #( - #child_ids => #child_dep_state_fields, - )* - #( - #child_state_ranges => { - self.#child_state_idents.update_child_dep_state( - ty - #sum_idents, - node, - vdom, - &children.iter().map(|p| &p.#child_state_idents).collect::>(), - ctx, - ).map(|mut changed|{ - for id in &mut changed.node_dep{ - *id += #sum_idents; - } - for id in &mut changed.child_dep{ - *id += #sum_idents; - } - changed - }) - } - )* - _ => panic!("{:?} not in {}", ty, #type_name_str), + impl Ord for HeightOrdering { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.height.cmp(&other.height).then(self.id.0.cmp(&other.id.0)) + } } - } - fn child_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec{ - let mut dep_types = Vec::new(); - #(if #child_types::NODE_MASK.overlaps(mask) { - dep_types.push(dioxus_native_core::state::MemberId(#child_ids_clone)); - })* - #( - dep_types.extend(self.#child_state_idents.child_dep_types(mask).into_iter().map(|id| id + #sum_idents)); - )* - dep_types - } + impl PartialOrd for HeightOrdering { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(&other)) + } + } - fn parent_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec{ - let mut dep_types = Vec::new(); - #(if #parent_types::NODE_MASK.overlaps(mask) { - dep_types.push(dioxus_native_core::state::MemberId(#parent_ids_clone)); - })* - #( - dep_types.extend(self.#child_state_idents.parent_dep_types(mask).into_iter().map(|id| id + #sum_idents)); - )* - dep_types - } + struct MembersDirty { + #(#members: bool, )* + } + + impl MembersDirty { + fn new() -> Self { + Self {#(#members: false),*} + } + + fn any(&self) -> bool { + #(self.#members || )* false + } + } + + let mut dirty_elements = fxhash::FxHashSet::default(); + // the states of any elements that are dirty + let mut states = fxhash::FxHashMap::default(); + + for (id, mask) in dirty { + let members_dirty = MembersDirty { + #(#members: #member_types::NODE_MASK.overlaps(mask),)* + }; + if members_dirty.any(){ + states.insert(*id, members_dirty); + } + dirty_elements.insert(*id); + } - fn node_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec{ - let mut dep_types = Vec::new(); - #(if #node_types::NODE_MASK.overlaps(mask) { - dep_types.push(dioxus_native_core::state::MemberId(#node_ids_clone)); - })* #( - dep_types.extend(self.#child_state_idents.node_dep_types(mask).into_iter().map(|id| id + #sum_idents)); + #resolve_members; )* - dep_types + + #( + dirty_elements.extend( + <#child_types as dioxus_native_core::state::State>::update( + dirty, + &mut state_tree.map(|n| &n.#child_members, |n| &mut n.#child_members), + vdom, + ctx, + ) + ); + )* + + dirty_elements } } }; @@ -282,6 +154,12 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { } } +struct Depenadants<'a> { + node: Vec<&'a Member>, + child: Vec<&'a Member>, + parent: Vec<&'a Member>, +} + struct Struct { name: Ident, members: Vec, @@ -302,7 +180,7 @@ struct StateStruct<'a> { impl<'a> StateStruct<'a> { fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result { let mut parse_err = Ok(()); - let state_members = strct + let mut unordered_state_members: Vec<_> = strct .members .iter() .zip(fields.iter()) @@ -312,7 +190,44 @@ impl<'a> StateStruct<'a> { parse_err = Err(err); None } - }); + }) + .collect(); + parse_err?; + + let mut state_members = Vec::new(); + while !unordered_state_members.is_empty() { + let mut resolved = false; + for i in 0..unordered_state_members.len() { + let mem = &mut unordered_state_members[i]; + if mem.dep_mems.iter().all(|(dep, resolved)| { + *resolved || (*dep == mem.mem && mem.dep_kind != DepKind::Node) + }) { + let mem = unordered_state_members.remove(i); + // mark any dependancy that depends on this member as resolved + for member in unordered_state_members.iter_mut() { + for (dep, resolved) in &mut member.dep_mems { + *resolved |= *dep == mem.mem; + } + } + state_members.push(mem); + resolved = true; + break; + } + } + if !resolved { + return Err(Error::new( + strct.name.span(), + format!( + "{} has circular dependacy in {:?}", + strct.name, + unordered_state_members + .iter() + .map(|m| format!("{}", &m.mem.ident)) + .collect::>() + ), + )); + } + } let child_states = strct .members @@ -328,177 +243,234 @@ impl<'a> StateStruct<'a> { }) .map(|(m, _)| m); - #[derive(Debug, Clone)] - struct DepNode<'a> { - state_mem: StateMember<'a>, - depandants: Vec>, - } - impl<'a> DepNode<'a> { - fn new(state_mem: StateMember<'a>) -> Self { - Self { - state_mem, - depandants: Vec::new(), - } - } - - /// flattens the node in pre order - fn flatten(self) -> Vec> { - let DepNode { - state_mem, - depandants, - } = self; - let mut flat = vec![state_mem]; - for d in depandants { - flat.append(&mut d.flatten()); - } - flat - } - - fn set_ids(&mut self, current_id: &mut usize) { - self.state_mem.member_id = dioxus_native_core::state::MemberId(*current_id); - // if the node depends on itself, we need to add the dependency seperately - if let Some(dep) = self.state_mem.dep_mem { - if dep == self.state_mem.mem { - self.state_mem - .dependants - .push((MemberId(*current_id), self.state_mem.dep_kind.clone())); - } - } - *current_id += 1; - for d in &mut self.depandants { - self.state_mem - .dependants - .push((MemberId(*current_id), d.state_mem.dep_kind.clone())); - d.set_ids(current_id); - } - } - - fn contains_member(&self, member: &Member) -> bool { - if self.state_mem.mem == member { - true - } else { - self.depandants.iter().any(|d| d.contains_member(member)) - } - } - - // check if there are any mixed child/parent dependancies - fn check(&self) -> Option { - self.kind().err() - } - - fn kind(&self) -> Result<&DepKind> { - fn reduce_kind<'a>(dk1: &'a DepKind, dk2: &'a DepKind) -> Result<&'a DepKind> { - match (dk1, dk2) { - (DepKind::Child, DepKind::Parent) | (DepKind::Parent, DepKind::Child) => { - Err(Error::new( - Span::call_site(), - "There is a ChildDepState that depends on a ParentDepState", - )) - } - // node dep state takes the lowest priority - (DepKind::Node, important) | (important, DepKind::Node) => Ok(important), - // they are the same - (fst, _) => Ok(fst), - } - } - reduce_kind( - self.depandants - .iter() - .try_fold(&DepKind::Node, |dk1, dk2| reduce_kind(dk1, dk2.kind()?))?, - &self.state_mem.dep_kind, - ) - } - - fn insert_dependant(&mut self, other: DepNode<'a>) -> bool { - let dep = other.state_mem.dep_mem.unwrap(); - if self.contains_member(dep) { - if self.state_mem.mem == dep { - self.depandants.push(other); - true - } else { - self.depandants - .iter_mut() - .find(|d| d.contains_member(dep)) - .unwrap() - .insert_dependant(other) - } - } else { - false - } - } - } - // members need to be sorted so that members are updated after the members they depend on - let mut roots: Vec = vec![]; - for m in state_members { - if let Some(dep) = m.dep_mem { - let root_depends_on = roots - .iter() - .filter_map(|m| m.state_mem.dep_mem) - .any(|d| m.mem == d); + Ok(Self { + state_members, + child_states: child_states.collect(), + }) + } - if let Some(r) = roots.iter_mut().find(|r| r.contains_member(dep)) { - let new = DepNode::new(m); - if root_depends_on { - return Err(Error::new( - new.state_mem.mem.ident.span(), - format!("{} has a circular dependancy", new.state_mem.mem.ident), - )); + fn get_depenadants(&self, mem: &Member) -> Depenadants { + let mut dependants = Depenadants { + node: Vec::new(), + child: Vec::new(), + parent: Vec::new(), + }; + for member in &self.state_members { + for (dep, _) in &member.dep_mems { + if *dep == mem { + match member.dep_kind { + DepKind::Node => dependants.node.push(member.mem), + DepKind::Child => dependants.child.push(member.mem), + DepKind::Parent => dependants.parent.push(member.mem), } - // return Err(Error::new(new.state_mem.mem.ident.span(), "stuff")); - r.insert_dependant(new); - continue; } } - let mut new = DepNode::new(m); - let mut i = 0; - while i < roots.len() { - if roots[i].state_mem.dep_mem == Some(new.state_mem.mem) { - let child = roots.remove(i); - new.insert_dependant(child); - } else { - i += 1; - } - } - roots.push(new); } - parse_err?; - let mut current_id = 0; - for r in &mut roots { - r.set_ids(&mut current_id); - } - if let Some(err) = roots.iter().find_map(DepNode::check) { - Err(err) - } else { - let state_members: Vec<_> = roots - .into_iter() - .flat_map(|r| r.flatten().into_iter()) - .collect(); + dependants + } - Ok(Self { - state_members, - child_states: child_states.collect(), - }) + fn update_dependants(&self, mem: &Member) -> impl ToTokens { + let dep = self.get_depenadants(mem); + let update_child_dependants = if dep.child.is_empty() { + quote!() + } else { + let insert = dep.child.iter().map(|d|{ + if *d == mem { + quote! { + let seeking = HeightOrdering::new(state_tree.height(parent_id).unwrap(), parent_id); + if let Err(idx) = resolution_order + .binary_search_by(|ordering| ordering.cmp(&seeking).reverse()){ + resolution_order.insert( + idx, + seeking, + ); + } + } + } else { + quote! {} + } + }); + let update: Vec<_> = dep + .child + .iter() + .map(|d| { + let ident = &d.ident; + quote! { + dirty.#ident = true; + } + }) + .collect(); + quote! { + if let Some(parent_id) = state_tree.parent(id) { + #(#insert)* + if let Some(dirty) = states.get_mut(&parent_id) { + #(#update)* + } + else { + let mut dirty = MembersDirty::new(); + #(#update)* + states.insert(parent_id, dirty); + } + } + } + }; + let node_dependants: Vec<_> = dep.node.iter().map(|d| &d.ident).collect(); + let update_node_dependants = quote! {#(members_dirty.#node_dependants = true;)*}; + let update_parent_dependants = if dep.parent.is_empty() { + quote!() + } else { + let insert = dep.parent.iter().map(|d| { + if *d == mem { + quote! { + let seeking = HeightOrdering::new(state_tree.height(*child_id).unwrap(), *child_id); + if let Err(idx) = resolution_order + .binary_search(&seeking){ + resolution_order.insert( + idx, + seeking, + ); + } + } + } else { + quote! {} + } + }); + let update: Vec<_> = dep + .parent + .iter() + .map(|d| { + let ident = &d.ident; + quote! { + dirty.#ident = true; + } + }) + .collect(); + quote! { + for child_id in state_tree.children(id) { + #(#insert)* + if let Some(dirty) = states.get_mut(&child_id) { + #(#update)* + } + else { + let mut dirty = MembersDirty::new(); + #(#update)* + states.insert(*child_id, dirty); + } + } + } + }; + + quote! { + #update_node_dependants + #update_child_dependants + #update_parent_dependants + } + } + + fn resolve(&self, mem: &StateMember) -> impl ToTokens { + let reduce_member = mem.reduce_self(); + let update_dependant = self.update_dependants(mem.mem); + let member = &mem.mem.ident; + + match mem.dep_kind { + DepKind::Parent => { + quote! { + // resolve parent dependant state + let mut resolution_order = states.keys().copied().map(|id| HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::>(); + resolution_order.sort(); + let mut i = 0; + while i < resolution_order.len(){ + let id = resolution_order[i].id; + let vnode = vdom.get_element(id).unwrap(); + let members_dirty = states.get_mut(&id).unwrap(); + let (current_state, parent) = state_tree.get_node_parent_mut(id); + let current_state = current_state.unwrap(); + if members_dirty.#member && #reduce_member { + dirty_elements.insert(id); + #update_dependant + } + i += 1; + } + } + } + DepKind::Child => { + quote! { + // resolve child dependant state + let mut resolution_order = states.keys().copied().map(|id| HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::>(); + resolution_order.sort_by(|height_ordering1, height_ordering2| { + height_ordering1.cmp(&height_ordering2).reverse() + }); + let mut i = 0; + while i < resolution_order.len(){ + let id = resolution_order[i].id; + let vnode = vdom.get_element(id).unwrap(); + let members_dirty = states.get_mut(&id).unwrap(); + let (current_state, children) = state_tree.get_node_children_mut(id); + let current_state = current_state.unwrap(); + if members_dirty.#member && #reduce_member { + dirty_elements.insert(id); + #update_dependant + } + i += 1; + } + } + } + DepKind::Node => { + quote! { + // resolve node dependant state + let mut resolution_order = states.keys().copied().collect::>(); + let mut i = 0; + while i < resolution_order.len(){ + let id = resolution_order[i]; + let vnode = vdom.get_element(id).unwrap(); + let members_dirty = states.get_mut(&id).unwrap(); + let current_state = state_tree.get_mut(id).unwrap(); + if members_dirty.#member && #reduce_member { + dirty_elements.insert(id); + #update_dependant + } + i += 1; + } + } + } } } } +fn try_parenthesized(input: ParseStream) -> Result { + let inside; + parenthesized!(inside in input); + Ok(inside) +} + struct Dependancy { ctx_ty: Option, - dep: Option, + deps: Vec, } impl Parse for Dependancy { fn parse(input: ParseStream) -> Result { - let dep = input - .parse() - .ok() - .filter(|i: &Ident| format!("{}", i) != "NONE"); + let deps: Option> = { + try_parenthesized(input) + .ok() + .and_then(|inside| inside.parse_terminated(Ident::parse).ok()) + }; + let deps: Vec<_> = deps + .map(|deps| deps.into_iter().collect()) + .or_else(|| { + input + .parse::() + .ok() + .filter(|i: &Ident| format!("{}", i) != "NONE") + .map(|i| vec![i]) + }) + .unwrap_or_default(); let comma: Option = input.parse().ok(); let ctx_ty = input.parse().ok(); Ok(Self { ctx_ty: comma.and(ctx_ty), - dep, + deps, }) } } @@ -522,11 +494,9 @@ impl Member { struct StateMember<'a> { mem: &'a Member, dep_kind: DepKind, - dep_mem: Option<&'a Member>, + // the depenancy and if it is satified + dep_mems: Vec<(&'a Member, bool)>, ctx_ty: Option, - dependants: Vec<(dioxus_native_core::state::MemberId, DepKind)>, - // This is just the index of the final order of the struct it is used to communicate which parts need updated and what order to update them in. - member_id: dioxus_native_core::state::MemberId, } impl<'a> StateMember<'a> { @@ -548,26 +518,26 @@ impl<'a> StateMember<'a> { })?; match a.parse_args::() { Ok(dependancy) => { - let dep_mem = if let Some(name) = &dependancy.dep { - if let Some(found) = parent.members.iter().find(|m| &m.ident == name) { - Some(found) - } else { - err = Err(Error::new( - name.span(), - format!("{} not found in {}", name, parent.name), - )); - None - } - } else { - None - }; + let dep_mems = dependancy + .deps + .iter() + .filter_map(|name| { + if let Some(found) = parent.members.iter().find(|m| &m.ident == name) { + Some((found, false)) + } else { + err = Err(Error::new( + name.span(), + format!("{} not found in {}", name, parent.name), + )); + None + } + }) + .collect(); Some(Self { mem, dep_kind, - dep_mem, + dep_mems, ctx_ty: dependancy.ctx_ty, - dependants: Vec::new(), - member_id: dioxus_native_core::state::MemberId(0), }) } Err(e) => { @@ -592,111 +562,26 @@ impl<'a> StateMember<'a> { } else { quote! {&()} }; - let states_changed = { - let child_dep = self - .dependants - .iter() - .filter(|(_, kind)| kind == &DepKind::Child) - .map(|(id, _)| id.0); - let parent_dep = self - .dependants - .iter() - .filter(|(_, kind)| kind == &DepKind::Parent) - .map(|(id, _)| id.0); - let node_dep = self - .dependants - .iter() - .filter(|(_, kind)| kind == &DepKind::Node) - .map(|(id, _)| id.0); - match self.dep_kind { - DepKind::Node => { - quote! { - dioxus_native_core::state::NodeStatesChanged{ - node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*], - } - } - } - DepKind::Child => { - quote! { - dioxus_native_core::state::ChildStatesChanged{ - node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*], - child_dep: vec![#(dioxus_native_core::state::MemberId(#child_dep), )*], - } - } - } - DepKind::Parent => { - quote! { - dioxus_native_core::state::ParentStatesChanged{ - node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*], - parent_dep: vec![#(dioxus_native_core::state::MemberId(#parent_dep), )*], - } - } - } - } - }; let ty = &self.mem.ty; let node_view = - quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK, vdom)); - if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) { - match self.dep_kind { - DepKind::Node => { - quote!({ - if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){ - Some(#states_changed) - } else{ - None - } - }) - } - DepKind::Child => { - quote!({ - if self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx){ - Some(#states_changed) - } else{ - None - } - }) - } - DepKind::Parent => { - quote!({ - if self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx){ - Some(#states_changed) - } else{ - None - } - }) - } + quote!(dioxus_native_core::node_ref::NodeView::new(vnode, #ty::NODE_MASK, vdom)); + let dep_idents = self.dep_mems.iter().map(|m| &m.0.ident); + match self.dep_kind { + DepKind::Node => { + quote!({ + current_state.#ident.reduce(#node_view, (#(¤t_state.#dep_idents,)*), #get_ctx) + }) } - } else { - match self.dep_kind { - DepKind::Node => { - quote!({ - if self.#ident.reduce(#node_view, &(), #get_ctx){ - Some(#states_changed) - } else{ - None - } - }) - } - DepKind::Child => { - quote!({ - if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){ - Some(#states_changed) - } else{ - None - } - }) - } - DepKind::Parent => { - quote!({ - if self.#ident.reduce(#node_view, Some(&()), #get_ctx){ - Some(#states_changed) - } else{ - None - } - }) - } + DepKind::Child => { + quote!({ + current_state.#ident.reduce(#node_view, children.iter().map(|c| (#(&c.#dep_idents)*)), #get_ctx) + }) + } + DepKind::Parent => { + quote!({ + current_state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.#dep_idents)*)), #get_ctx) + }) } } } diff --git a/packages/native-core-macro/tests/called_minimally_on_build.rs b/packages/native-core-macro/tests/called_minimally_on_build.rs new file mode 100644 index 000000000..390bfc7bc --- /dev/null +++ b/packages/native-core-macro/tests/called_minimally_on_build.rs @@ -0,0 +1,256 @@ +use anymap::AnyMap; +use dioxus::core as dioxus_core; +use dioxus::prelude::*; +use dioxus_native_core::node_ref::*; +use dioxus_native_core::real_dom::*; +use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; +use dioxus_native_core_macro::State; + +macro_rules! dep { + ( child( $name:ty, $dep:ty ) ) => { + impl ChildDepState for $name { + type Ctx = (); + type DepState = $dep; + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce<'a>( + &mut self, + _: NodeView, + _: impl Iterator, + _: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { + self.0 += 1; + true + } + } + }; + + ( parent( $name:ty, $dep:ty ) ) => { + impl ParentDepState for $name { + type Ctx = (); + type DepState = $dep; + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce( + &mut self, + _: NodeView, + _: Option<&Self::DepState>, + _: &Self::Ctx, + ) -> bool { + self.0 += 1; + true + } + } + }; + + ( node( $name:ty, ($($l:lifetime),*), $dep:ty ) ) => { + impl<$($l),*> NodeDepState<$dep> for $name { + type Ctx = (); + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce( + &mut self, + _: NodeView, + _: $dep, + _: &Self::Ctx, + ) -> bool { + self.0 += 1; + true + } + } + }; +} + +macro_rules! test_state{ + ( $s:ty, child: ( $( $child:ident ),* ), node: ( $( $node:ident ),* ), parent: ( $( $parent:ident ),* ) ) => { + #[test] + fn state_reduce_initally_called_minimally() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div { + div{ + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + }) + } + + let vdom = VirtualDom::new(Base); + + let mutations = vdom.create_vnodes(rsx! { + div { + div{ + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + } + }); + + let mut dom: RealDom<$s> = RealDom::new(); + + let nodes_updated = dom.apply_mutations(vec![mutations]); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); + + dom.traverse_depth_first(|n| { + $( + assert_eq!(n.state.$child.0, 1); + )* + $( + assert_eq!(n.state.$node.0, 1); + )* + $( + assert_eq!(n.state.$parent.0, 1); + )* + }); + } + } +} + +mod node_depends_on_child_and_parent { + use super::*; + #[derive(Debug, Clone, Default, PartialEq)] + struct Node(i32); + dep!(node(Node, ('a, 'b), (&'a Child, &'b Parent))); + + #[derive(Debug, Clone, Default, PartialEq)] + struct Child(i32); + dep!(child(Child, Child)); + + #[derive(Debug, Clone, Default, PartialEq)] + struct Parent(i32); + dep!(parent(Parent, Parent)); + + #[derive(Debug, Clone, Default, State)] + struct StateTester { + #[node_dep_state((child, parent))] + node: Node, + #[child_dep_state(child)] + child: Child, + #[parent_dep_state(parent)] + parent: Parent, + } + + test_state!(StateTester, child: (child), node: (node), parent: (parent)); +} + +mod child_depends_on_node_that_depends_on_parent { + use super::*; + #[derive(Debug, Clone, Default, PartialEq)] + struct Node(i32); + dep!(node(Node, ('a), (&'a Parent,))); + + #[derive(Debug, Clone, Default, PartialEq)] + struct Child(i32); + dep!(child(Child, Node)); + + #[derive(Debug, Clone, Default, PartialEq)] + struct Parent(i32); + dep!(parent(Parent, Parent)); + + #[derive(Debug, Clone, Default, State)] + struct StateTester { + #[node_dep_state(parent)] + node: Node, + #[child_dep_state(node)] + child: Child, + #[parent_dep_state(parent)] + parent: Parent, + } + + test_state!(StateTester, child: (child), node: (node), parent: (parent)); +} + +mod parent_depends_on_node_that_depends_on_child { + use super::*; + #[derive(Debug, Clone, Default, PartialEq)] + struct Node(i32); + dep!(node(Node, ('a), (&'a Child,))); + + #[derive(Debug, Clone, Default, PartialEq)] + struct Child(i32); + dep!(child(Child, Child)); + + #[derive(Debug, Clone, Default, PartialEq)] + struct Parent(i32); + dep!(parent(Parent, Node)); + + #[derive(Debug, Clone, Default, State)] + struct StateTester { + #[node_dep_state(child)] + node: Node, + #[child_dep_state(child)] + child: Child, + #[parent_dep_state(node)] + parent: Parent, + } + + test_state!(StateTester, child: (child), node: (node), parent: (parent)); +} + +mod node_depends_on_other_node_state { + use super::*; + #[derive(Debug, Clone, Default, PartialEq)] + struct Node1(i32); + dep!(node(Node1, ('a), (&'a Node2,))); + + #[derive(Debug, Clone, Default, PartialEq)] + struct Node2(i32); + dep!(node(Node2, (), ())); + + #[derive(Debug, Clone, Default, State)] + struct StateTester { + #[node_dep_state((node2))] + node1: Node1, + #[node_dep_state()] + node2: Node2, + } + + test_state!(StateTester, child: (), node: (node1, node2), parent: ()); +} + +mod node_child_and_parent_state_depends_on_self { + use super::*; + #[derive(Debug, Clone, Default, PartialEq)] + struct Node(i32); + dep!(node(Node, (), ())); + + #[derive(Debug, Clone, Default, PartialEq)] + struct Child(i32); + dep!(child(Child, Child)); + + #[derive(Debug, Clone, Default, PartialEq)] + struct Parent(i32); + dep!(parent(Parent, Parent)); + + #[derive(Debug, Clone, Default, State)] + struct StateTester { + #[node_dep_state()] + node: Node, + #[child_dep_state(child)] + child: Child, + #[parent_dep_state(parent)] + parent: Parent, + } + + test_state!(StateTester, child: (child), node: (node), parent: (parent)); +} diff --git a/packages/native-core-macro/tests/change_nodes.rs b/packages/native-core-macro/tests/change_nodes.rs index 93cb8bce8..56d31709b 100644 --- a/packages/native-core-macro/tests/change_nodes.rs +++ b/packages/native-core-macro/tests/change_nodes.rs @@ -51,8 +51,8 @@ fn remove_node() { assert_eq!(dom.size(), 2); assert!(&dom.contains_node(&VNode::Element(&root_div))); - assert_eq!(dom[1].height, 1); - assert_eq!(dom[2].height, 2); + assert_eq!(dom[ElementId(1)].height, 1); + assert_eq!(dom[ElementId(2)].height, 2); let vdom = VirtualDom::new(Base); let mutations = vdom.diff_lazynodes( @@ -80,7 +80,7 @@ fn remove_node() { assert_eq!(dom.size(), 1); assert!(&dom.contains_node(&VNode::Element(&new_root_div))); - assert_eq!(dom[1].height, 1); + assert_eq!(dom[ElementId(1)].height, 1); } #[test] @@ -113,7 +113,7 @@ fn add_node() { assert_eq!(dom.size(), 1); assert!(&dom.contains_node(&VNode::Element(&root_div))); - assert_eq!(dom[1].height, 1); + assert_eq!(dom[ElementId(1)].height, 1); let vdom = VirtualDom::new(Base); let mutations = vdom.diff_lazynodes( @@ -152,6 +152,6 @@ fn add_node() { assert_eq!(dom.size(), 2); assert!(&dom.contains_node(&VNode::Element(&new_root_div))); - assert_eq!(dom[1].height, 1); - assert_eq!(dom[2].height, 2); + assert_eq!(dom[ElementId(1)].height, 1); + assert_eq!(dom[ElementId(2)].height, 2); } diff --git a/packages/native-core-macro/tests/initial_build.rs b/packages/native-core-macro/tests/initial_build.rs index 5f2fba7ce..485ce81f0 100644 --- a/packages/native-core-macro/tests/initial_build.rs +++ b/packages/native-core-macro/tests/initial_build.rs @@ -40,7 +40,7 @@ fn initial_build_simple() { }; assert_eq!(dom.size(), 1); assert!(&dom.contains_node(&VNode::Element(&root_div))); - assert_eq!(dom[1].height, 1); + assert_eq!(dom[ElementId(1)].height, 1); } #[test] @@ -119,10 +119,10 @@ fn initial_build_with_children() { }; assert_eq!(dom.size(), 6); assert!(&dom.contains_node(&VNode::Element(&root_div))); - assert_eq!(dom[1].height, 1); - assert_eq!(dom[2].height, 2); - assert_eq!(dom[3].height, 3); - assert_eq!(dom[4].height, 3); - assert_eq!(dom[5].height, 4); - assert_eq!(dom[6].height, 3); + assert_eq!(dom[ElementId(1)].height, 1); + assert_eq!(dom[ElementId(2)].height, 2); + assert_eq!(dom[ElementId(3)].height, 3); + assert_eq!(dom[ElementId(4)].height, 3); + assert_eq!(dom[ElementId(5)].height, 4); + assert_eq!(dom[ElementId(6)].height, 3); } diff --git a/packages/native-core-macro/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs index 28440803b..25e4dec47 100644 --- a/packages/native-core-macro/tests/update_state.rs +++ b/packages/native-core-macro/tests/update_state.rs @@ -1,5 +1,6 @@ use anymap::AnyMap; use dioxus::core as dioxus_core; +use dioxus::core::ElementId; use dioxus::core::{AttributeValue, DomEdit, Mutations}; use dioxus::prelude::*; use dioxus_native_core::node_ref::*; @@ -49,13 +50,14 @@ impl ChildDepState for ChildDepCallCounter { const NODE_MASK: NodeMask = NodeMask::ALL; fn reduce<'a>( &mut self, - _: NodeView, + node: NodeView, _: impl Iterator, _: &Self::Ctx, ) -> bool where Self::DepState: 'a, { + println!("{self:?} {:?}: {} {:?}", node.tag(), node.id(), node.text()); self.0 += 1; true } @@ -80,11 +82,10 @@ impl ParentDepState for ParentDepCallCounter { #[derive(Debug, Clone, Default)] struct NodeDepCallCounter(u32); -impl NodeDepState for NodeDepCallCounter { +impl NodeDepState<()> for NodeDepCallCounter { type Ctx = (); - type DepState = (); const NODE_MASK: NodeMask = NodeMask::ALL; - fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { + fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool { self.0 += 1; true } @@ -133,11 +134,10 @@ impl ParentDepState for PushedDownStateTester { #[derive(Debug, Clone, PartialEq, Default)] struct NodeStateTester(Option, Vec<(String, String)>); -impl NodeDepState for NodeStateTester { +impl NodeDepState<()> for NodeStateTester { type Ctx = u32; - type DepState = (); const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag(); - fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, ctx: &Self::Ctx) -> bool { + fn reduce(&mut self, node: NodeView, _sibling: (), ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = NodeStateTester( node.tag().map(|s| s.to_string()), @@ -187,7 +187,7 @@ fn state_initial() { ctx.insert(42u32); let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx); - let root_div = &dom[1]; + let root_div = &dom[ElementId(1)]; assert_eq!(root_div.state.bubbled.0, Some("div".to_string())); assert_eq!( root_div.state.bubbled.1, @@ -204,7 +204,7 @@ fn state_initial() { assert_eq!(root_div.state.node.0, Some("div".to_string())); assert_eq!(root_div.state.node.1, vec![]); - let child_p = &dom[2]; + let child_p = &dom[ElementId(2)]; assert_eq!(child_p.state.bubbled.0, Some("p".to_string())); assert_eq!(child_p.state.bubbled.1, Vec::new()); assert_eq!(child_p.state.pushed.0, Some("p".to_string())); @@ -221,7 +221,7 @@ fn state_initial() { vec![("color".to_string(), "red".to_string())] ); - let child_h1 = &dom[3]; + let child_h1 = &dom[ElementId(3)]; assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string())); assert_eq!(child_h1.state.bubbled.1, Vec::new()); assert_eq!(child_h1.state.pushed.0, Some("h1".to_string())); @@ -236,64 +236,6 @@ fn state_initial() { assert_eq!(child_h1.state.node.1, vec![]); } -#[test] -fn state_reduce_initally_called_minimally() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - rsx!(cx, div { - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - }) - } - - let vdom = VirtualDom::new(Base); - - let mutations = vdom.create_vnodes(rsx! { - div { - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - } - }); - - let mut dom: RealDom = RealDom::new(); - - let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); - - dom.traverse_depth_first(|n| { - assert_eq!(n.state.part1.child_counter.0, 1); - assert_eq!(n.state.child_counter.0, 1); - assert_eq!(n.state.part2.parent_counter.0, 1); - assert_eq!(n.state.parent_counter.0, 1); - assert_eq!(n.state.part3.node_counter.0, 1); - assert_eq!(n.state.node_counter.0, 1); - }); -} - #[test] fn state_reduce_parent_called_minimally_on_update() { #[allow(non_snake_case)] @@ -446,11 +388,15 @@ struct UnorderedDependanciesState { #[derive(Debug, Clone, Default, PartialEq)] struct ADepCallCounter(usize, BDepCallCounter); -impl NodeDepState for ADepCallCounter { +impl<'a> NodeDepState<(&'a BDepCallCounter,)> for ADepCallCounter { type Ctx = (); - type DepState = BDepCallCounter; const NODE_MASK: NodeMask = NodeMask::NONE; - fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { + fn reduce( + &mut self, + _node: NodeView, + (sibling,): (&'a BDepCallCounter,), + _ctx: &Self::Ctx, + ) -> bool { self.0 += 1; self.1 = sibling.clone(); true @@ -459,11 +405,15 @@ impl NodeDepState for ADepCallCounter { #[derive(Debug, Clone, Default, PartialEq)] struct BDepCallCounter(usize, CDepCallCounter); -impl NodeDepState for BDepCallCounter { +impl<'a> NodeDepState<(&'a CDepCallCounter,)> for BDepCallCounter { type Ctx = (); - type DepState = CDepCallCounter; const NODE_MASK: NodeMask = NodeMask::NONE; - fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { + fn reduce( + &mut self, + _node: NodeView, + (sibling,): (&'a CDepCallCounter,), + _ctx: &Self::Ctx, + ) -> bool { self.0 += 1; self.1 = sibling.clone(); true @@ -472,11 +422,10 @@ impl NodeDepState for BDepCallCounter { #[derive(Debug, Clone, Default, PartialEq)] struct CDepCallCounter(usize); -impl NodeDepState for CDepCallCounter { +impl NodeDepState<()> for CDepCallCounter { type Ctx = (); - type DepState = (); const NODE_MASK: NodeMask = NodeMask::ALL; - fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { + fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool { self.0 += 1; true } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 2319ca4ac..00110163c 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -2,4 +2,6 @@ pub mod layout_attributes; pub mod node_ref; pub mod real_dom; pub mod state; +#[doc(hidden)] +pub mod traversable; pub mod utils; diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index b1e1b5e1d..9ef83619f 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -70,7 +70,7 @@ impl<'a> NodeView<'a> { } } -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] pub enum AttributeMask { All, Dynamic(Vec<&'static str>), @@ -175,7 +175,7 @@ impl Default for AttributeMask { } } -#[derive(Default, PartialEq, Clone, Debug)] +#[derive(Default, PartialEq, Eq, Clone, Debug)] pub struct NodeMask { // must be sorted attritutes: AttributeMask, diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index ca8da730a..b02508baa 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,17 +1,12 @@ use anymap::AnyMap; use fxhash::{FxHashMap, FxHashSet}; -use std::{ - collections::VecDeque, - ops::{Index, IndexMut}, -}; +use std::ops::{Index, IndexMut}; use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; -use crate::state::{union_ordered_iter, State}; -use crate::{ - node_ref::{AttributeMask, NodeMask}, - state::MemberId, -}; +use crate::node_ref::{AttributeMask, NodeMask}; +use crate::state::State; +use crate::traversable::Traversable; /// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers. /// The render state passes from parent to children and or accumulates state from children to parents. @@ -20,7 +15,7 @@ use crate::{ pub struct RealDom { root: usize, nodes: Vec>>, - nodes_listening: FxHashMap<&'static str, FxHashSet>, + nodes_listening: FxHashMap<&'static str, FxHashSet>, node_stack: smallvec::SmallVec<[usize; 10]>, } @@ -51,7 +46,7 @@ impl RealDom { } /// Updates the dom, up and down state and return a set of nodes that were updated pass this to update_state. - pub fn apply_mutations(&mut self, mutations_vec: Vec) -> Vec<(usize, NodeMask)> { + pub fn apply_mutations(&mut self, mutations_vec: Vec) -> Vec<(ElementId, NodeMask)> { let mut nodes_updated = Vec::new(); for mutations in mutations_vec { for e in mutations.edits { @@ -72,40 +67,44 @@ impl RealDom { .drain(self.node_stack.len() - many as usize..) .collect(); for ns in drained { - self.link_child(ns, target).unwrap(); - nodes_updated.push((ns, NodeMask::ALL)); + let id = ElementId(ns); + self.link_child(id, ElementId(target)).unwrap(); + nodes_updated.push((id, NodeMask::ALL)); } } ReplaceWith { root, m } => { - let root = self.remove(root as usize).unwrap(); + let root = self.remove(ElementId(root as usize)).unwrap(); let target = root.parent.unwrap().0; let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect(); for ns in drained { - nodes_updated.push((ns, NodeMask::ALL)); - self.link_child(ns, target).unwrap(); + let id = ElementId(ns); + nodes_updated.push((id, NodeMask::ALL)); + self.link_child(id, ElementId(target)).unwrap(); } } InsertAfter { root, n } => { - let target = self[root as usize].parent.unwrap().0; + let target = self[ElementId(root as usize)].parent.unwrap().0; let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect(); for ns in drained { - nodes_updated.push((ns, NodeMask::ALL)); - self.link_child(ns, target).unwrap(); + let id = ElementId(ns); + nodes_updated.push((id, NodeMask::ALL)); + self.link_child(id, ElementId(target)).unwrap(); } } InsertBefore { root, n } => { - let target = self[root as usize].parent.unwrap().0; + let target = self[ElementId(root as usize)].parent.unwrap().0; let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect(); for ns in drained { - nodes_updated.push((ns, NodeMask::ALL)); - self.link_child(ns, target).unwrap(); + let id = ElementId(ns); + nodes_updated.push((id, NodeMask::ALL)); + self.link_child(id, ElementId(target)).unwrap(); } } Remove { root } => { - if let Some(parent) = self[root as usize].parent { - nodes_updated.push((parent.0, NodeMask::NONE)); + if let Some(parent) = self[ElementId(root as usize)].parent { + nodes_updated.push((parent, NodeMask::NONE)); } - self.remove(root as usize).unwrap(); + self.remove(ElementId(root as usize)).unwrap(); } CreateTextNode { root, text } => { let n = Node::new( @@ -152,26 +151,29 @@ impl RealDom { scope: _, root, } => { - nodes_updated.push((root as usize, NodeMask::new().with_listeners())); + let id = ElementId(root as usize); + nodes_updated.push((id, NodeMask::new().with_listeners())); if let Some(v) = self.nodes_listening.get_mut(event_name) { - v.insert(root as usize); + v.insert(id); } else { let mut hs = FxHashSet::default(); - hs.insert(root as usize); + hs.insert(id); self.nodes_listening.insert(event_name, hs); } } RemoveEventListener { root, event } => { - nodes_updated.push((root as usize, NodeMask::new().with_listeners())); + let id = ElementId(root as usize); + nodes_updated.push((id, NodeMask::new().with_listeners())); let v = self.nodes_listening.get_mut(event).unwrap(); - v.remove(&(root as usize)); + v.remove(&id); } SetText { root, text: new_text, } => { - let target = &mut self[root as usize]; - nodes_updated.push((root as usize, NodeMask::new().with_text())); + let id = ElementId(root as usize); + let target = &mut self[id]; + nodes_updated.push((id, NodeMask::new().with_text())); match &mut target.node_type { NodeType::Text { text } => { *text = new_text.to_string(); @@ -180,18 +182,16 @@ impl RealDom { } } SetAttribute { root, field, .. } => { - nodes_updated.push(( - root as usize, - NodeMask::new_with_attrs(AttributeMask::single(field)), - )); + let id = ElementId(root as usize); + nodes_updated + .push((id, NodeMask::new_with_attrs(AttributeMask::single(field)))); } RemoveAttribute { root, name: field, .. } => { - nodes_updated.push(( - root as usize, - NodeMask::new_with_attrs(AttributeMask::single(field)), - )); + let id = ElementId(root as usize); + nodes_updated + .push((id, NodeMask::new_with_attrs(AttributeMask::single(field)))); } PopRoot {} => { self.node_stack.pop(); @@ -203,309 +203,60 @@ impl RealDom { nodes_updated } - /// Seperated from apply_mutations because Mutations require a mutable reference to the VirtualDom. pub fn update_state( &mut self, vdom: &VirtualDom, - nodes_updated: Vec<(usize, NodeMask)>, + nodes_updated: Vec<(ElementId, NodeMask)>, ctx: AnyMap, - ) -> Option> { - #[derive(PartialEq, Clone, Debug)] - enum StatesToCheck { - All, - Some(Vec), - } - impl StatesToCheck { - fn union(&self, other: &Self) -> Self { - match (self, other) { - (Self::Some(s), Self::Some(o)) => Self::Some(union_ordered_iter( - s.iter().copied(), - o.iter().copied(), - s.len() + o.len(), - )), - _ => Self::All, - } - } - } - - #[derive(Debug, Clone)] - struct NodeRef { - id: usize, - height: u16, - node_mask: NodeMask, - to_check: StatesToCheck, - } - impl NodeRef { - fn union_with(&mut self, other: &Self) { - self.node_mask = self.node_mask.union(&other.node_mask); - self.to_check = self.to_check.union(&other.to_check); - } - } - impl Eq for NodeRef {} - impl PartialEq for NodeRef { - fn eq(&self, other: &Self) -> bool { - self.id == other.id && self.height == other.height - } - } - impl Ord for NodeRef { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.partial_cmp(other).unwrap() - } - } - impl PartialOrd for NodeRef { - fn partial_cmp(&self, other: &Self) -> Option { - // Sort nodes first by height, then if the height is the same id. - // The order of the id does not matter it just helps with binary search later - Some(self.height.cmp(&other.height).then(self.id.cmp(&other.id))) - } - } - - let mut to_rerender = FxHashSet::default(); - to_rerender.extend(nodes_updated.iter().map(|(id, _)| id)); - let mut nodes_updated: Vec<_> = nodes_updated - .into_iter() - .map(|(id, mask)| NodeRef { - id, - height: self[id].height, - node_mask: mask, - to_check: StatesToCheck::All, - }) - .collect(); - nodes_updated.sort(); - - // Combine mutations that affect the same node. - let mut last_node: Option = None; - let mut new_nodes_updated = VecDeque::new(); - for current in nodes_updated.into_iter() { - if let Some(node) = &mut last_node { - if *node == current { - node.union_with(¤t); - } else { - new_nodes_updated.push_back(last_node.replace(current).unwrap()); - } - } else { - last_node = Some(current); - } - } - if let Some(node) = last_node { - new_nodes_updated.push_back(node); - } - let nodes_updated = new_nodes_updated; - - // update the state that only depends on nodes. The order does not matter. - for node_ref in &nodes_updated { - let mut changed = false; - let node = &mut self[node_ref.id]; - let mut ids = match &node_ref.to_check { - StatesToCheck::All => node.state.node_dep_types(&node_ref.node_mask), - // this should only be triggered from the current node, so all members will need to be checked - StatesToCheck::Some(_) => unreachable!(), - }; - let mut i = 0; - while i < ids.len() { - let id = ids[i]; - let node = &mut self[node_ref.id]; - let vnode = node.element(vdom); - if let Some(members_effected) = - node.state.update_node_dep_state(id, vnode, vdom, &ctx) - { - debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); - for m in &members_effected.node_dep { - if let Err(idx) = ids.binary_search(m) { - ids.insert(idx, *m); - } - } - changed = true; - } - i += 1; - } - if changed { - to_rerender.insert(node_ref.id); - } - } - - // bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up. - let mut to_bubble = nodes_updated.clone(); - while let Some(node_ref) = to_bubble.pop_back() { - let NodeRef { - id, - height, - node_mask, - to_check, - } = node_ref; - let (node, children) = self.get_node_children_mut(id).unwrap(); - let children_state: Vec<_> = children.iter().map(|c| &c.state).collect(); - let mut ids = match to_check { - StatesToCheck::All => node.state.child_dep_types(&node_mask), - StatesToCheck::Some(ids) => ids, - }; - let mut changed = Vec::new(); - let mut i = 0; - while i < ids.len() { - let id = ids[i]; - let vnode = node.element(vdom); - if let Some(members_effected) = - node.state - .update_child_dep_state(id, vnode, vdom, &children_state, &ctx) - { - debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); - for m in members_effected.node_dep { - if let Err(idx) = ids.binary_search(&m) { - ids.insert(idx, m); - } - } - for m in members_effected.child_dep { - changed.push(m); - } - } - i += 1; - } - if let Some(parent_id) = node.parent { - if !changed.is_empty() { - to_rerender.insert(id); - let i = to_bubble.partition_point( - |NodeRef { - id: other_id, - height: h, - .. - }| { - *h < height - 1 || (*h == height - 1 && *other_id < parent_id.0) - }, - ); - // make sure the parent is not already queued - if i < to_bubble.len() && to_bubble[i].id == parent_id.0 { - to_bubble[i].to_check = - to_bubble[i].to_check.union(&StatesToCheck::Some(changed)); - } else { - to_bubble.insert( - i, - NodeRef { - id: parent_id.0, - height: height - 1, - node_mask: NodeMask::NONE, - to_check: StatesToCheck::Some(changed), - }, - ); - } - } - } - } - - // push down state. To avoid calling reduce more times than nessisary start from the top and go down. - let mut to_push = nodes_updated; - while let Some(node_ref) = to_push.pop_front() { - let NodeRef { - id, - height, - node_mask, - to_check, - } = node_ref; - let node = &self[id]; - let mut ids = match to_check { - StatesToCheck::All => node.state.parent_dep_types(&node_mask), - StatesToCheck::Some(ids) => ids, - }; - let mut changed = Vec::new(); - let (node, parent) = self.get_node_parent_mut(id).unwrap(); - let mut i = 0; - while i < ids.len() { - let id = ids[i]; - let vnode = node.element(vdom); - let parent = parent.as_deref(); - let state = &mut node.state; - if let Some(members_effected) = - state.update_parent_dep_state(id, vnode, vdom, parent.map(|n| &n.state), &ctx) - { - debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); - for m in members_effected.node_dep { - if let Err(idx) = ids.binary_search(&m) { - ids.insert(idx, m); - } - } - for m in members_effected.parent_dep { - changed.push(m); - } - } - i += 1; - } - - to_rerender.insert(id); - if !changed.is_empty() { - let node = &self[id]; - if let NodeType::Element { children, .. } = &node.node_type { - for c in children { - let i = to_push.partition_point( - |NodeRef { - id: other_id, - height: h, - .. - }| { - *h < height + 1 || (*h == height + 1 && *other_id < c.0) - }, - ); - if i < to_push.len() && to_push[i].id == c.0 { - to_push[i].to_check = to_push[i] - .to_check - .union(&StatesToCheck::Some(changed.clone())); - } else { - to_push.insert( - i, - NodeRef { - id: c.0, - height: height + 1, - node_mask: NodeMask::NONE, - to_check: StatesToCheck::Some(changed.clone()), - }, - ); - } - } - } - } - } - - Some(to_rerender) + ) -> FxHashSet { + S::update( + &nodes_updated, + &mut self.map(|n| &n.state, |n| &mut n.state), + vdom, + &ctx, + ) } - fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> { + fn link_child(&mut self, child_id: ElementId, parent_id: ElementId) -> Option<()> { debug_assert_ne!(child_id, parent_id); let parent = &mut self[parent_id]; - parent.add_child(ElementId(child_id)); + parent.add_child(child_id); let parent_height = parent.height + 1; - self[child_id].set_parent(ElementId(parent_id)); + self[child_id].set_parent(parent_id); self.increase_height(child_id, parent_height); Some(()) } - fn increase_height(&mut self, id: usize, amount: u16) { + fn increase_height(&mut self, id: ElementId, amount: u16) { let n = &mut self[id]; n.height += amount; if let NodeType::Element { children, .. } = &n.node_type { for c in children.clone() { - self.increase_height(c.0, amount); + self.increase_height(c, amount); } } } // remove a node and it's children from the dom. - fn remove(&mut self, id: usize) -> Option> { + fn remove(&mut self, id: ElementId) -> Option> { // We do not need to remove the node from the parent's children list for children. - fn inner(dom: &mut RealDom, id: usize) -> Option> { - let mut node = dom.nodes[id as usize].take()?; + fn inner(dom: &mut RealDom, id: ElementId) -> Option> { + let mut node = dom.nodes[id.0].take()?; if let NodeType::Element { children, .. } = &mut node.node_type { for c in children { - inner(dom, c.0)?; + inner(dom, *c)?; } } Some(node) } - let mut node = self.nodes[id as usize].take()?; + let mut node = self.nodes[id.0].take()?; if let Some(parent) = node.parent { let parent = &mut self[parent]; - parent.remove_child(ElementId(id)); + parent.remove_child(id); } if let NodeType::Element { children, .. } = &mut node.node_type { for c in children { - inner(self, c.0)?; + inner(self, *c)?; } } Some(node) @@ -521,62 +272,6 @@ impl RealDom { self.nodes[id] = Some(node); } - pub fn get(&self, id: usize) -> Option<&Node> { - self.nodes.get(id)?.as_ref() - } - - pub fn get_mut(&mut self, id: usize) -> Option<&mut Node> { - self.nodes.get_mut(id)?.as_mut() - } - - // this is safe because no node will have itself as a child - pub fn get_node_children_mut( - &mut self, - id: usize, - ) -> Option<(&mut Node, Vec<&mut Node>)> { - let ptr = self.nodes.as_mut_ptr(); - unsafe { - if id >= self.nodes.len() { - None - } else { - let node = &mut *ptr.add(id); - if let Some(node) = node.as_mut() { - let children = match &node.node_type { - NodeType::Element { children, .. } => children - .iter() - .map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap()) - .collect(), - _ => Vec::new(), - }; - Some((node, children)) - } else { - None - } - } - } - } - - // this is safe because no node will have itself as a parent - pub fn get_node_parent_mut( - &mut self, - id: usize, - ) -> Option<(&mut Node, Option<&mut Node>)> { - let ptr = self.nodes.as_mut_ptr(); - unsafe { - let node = &mut *ptr.add(id); - if id >= self.nodes.len() { - None - } else if let Some(node) = node.as_mut() { - let parent = node - .parent - .map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap()); - Some((node, parent)) - } else { - None - } - } - } - pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node> { if let Some(nodes) = self.nodes_listening.get(event) { let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect(); @@ -658,7 +353,7 @@ impl RealDom { } } } - if let NodeType::Element { children, .. } = &self[self.root].node_type { + if let NodeType::Element { children, .. } = &self[ElementId(self.root)].node_type { for c in children { inner(self, *c, &mut f); } @@ -677,7 +372,7 @@ impl RealDom { } } let root = self.root; - if let NodeType::Element { children, .. } = &mut self[root].node_type { + if let NodeType::Element { children, .. } = &mut self[ElementId(root)].node_type { for c in children.clone() { inner(self, c, &mut f); } @@ -685,30 +380,17 @@ impl RealDom { } } -impl Index for RealDom { - type Output = Node; - - fn index(&self, idx: usize) -> &Self::Output { - self.get(idx).expect("Node does not exist") - } -} - impl Index for RealDom { type Output = Node; fn index(&self, idx: ElementId) -> &Self::Output { - &self[idx.0] + self.get(idx).unwrap() } } -impl IndexMut for RealDom { - fn index_mut(&mut self, idx: usize) -> &mut Self::Output { - self.get_mut(idx).expect("Node does not exist") - } -} impl IndexMut for RealDom { fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output { - &mut self[idx.0] + self.get_mut(idx).unwrap() } } @@ -772,3 +454,35 @@ impl Node { self.parent = Some(parent); } } + +impl Traversable for RealDom { + type Id = ElementId; + type Node = Node; + + fn height(&self, id: Self::Id) -> Option { + Some(::get(self, id)?.height) + } + + fn get(&self, id: Self::Id) -> Option<&Self::Node> { + self.nodes.get(id.0)?.as_ref() + } + + fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node> { + self.nodes.get_mut(id.0)?.as_mut() + } + + fn children(&self, id: Self::Id) -> &[Self::Id] { + if let Some(node) = ::get(self, id) { + match &node.node_type { + NodeType::Element { children, .. } => children, + _ => &[], + } + } else { + &[] + } + } + + fn parent(&self, id: Self::Id) -> Option { + ::get(self, id).and_then(|n| n.parent) + } +} diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 89c47e6e9..1cb4f5e14 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,13 +1,11 @@ -use std::{ - cmp::Ordering, - fmt::Debug, - ops::{Add, AddAssign, Sub, SubAssign}, -}; +use std::{cmp::Ordering, fmt::Debug}; use anymap::AnyMap; -use dioxus_core::VNode; +use dioxus_core::ElementId; +use fxhash::FxHashSet; use crate::node_ref::{NodeMask, NodeView}; +use crate::traversable::Traversable; pub(crate) fn union_ordered_iter( s_iter: impl Iterator, @@ -50,7 +48,7 @@ pub trait ChildDepState { /// The context is passed to the [ChildDepState::reduce] when it is pushed down. /// This is sometimes nessisary for lifetime purposes. type Ctx; - /// This must be either a [ChildDepState] or [NodeDepState] + /// This must be either a [ChildDepState], or [NodeDepState] type DepState; const NODE_MASK: NodeMask = NodeMask::NONE; fn reduce<'a>( @@ -73,70 +71,77 @@ pub trait ParentDepState { /// This must be either a [ParentDepState] or [NodeDepState] type DepState; const NODE_MASK: NodeMask = NodeMask::NONE; - fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool; + fn reduce<'a>( + &mut self, + node: NodeView, + parent: Option<&'a Self::DepState>, + ctx: &Self::Ctx, + ) -> bool; } /// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color. /// Called when the current node's node properties are modified or a sibling's [NodeDepState] is modified. /// Called at most once per update. -pub trait NodeDepState { +/// NodeDepState is the only state that can accept multiple dependancies, but only from the current node. +/// ```rust +/// impl<'a, 'b> NodeDepState<(&'a TextWrap, &'b ChildLayout)> for Layout { +/// type Ctx = LayoutCache; +/// const NODE_MASK: NodeMask = +/// NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!([ +/// "width", "height" +/// ]))) +/// .with_text(); +/// fn reduce<'a>( +/// &mut self, +/// node: NodeView, +/// siblings: (&'a TextWrap, &'b ChildLayout), +/// ctx: &Self::Ctx, +/// ) -> bool { +/// let old = self.clone(); +/// let (text_wrap, child_layout) = siblings; +/// if TextWrap::Wrap == text_wrap { +/// if let Some(text) = node.text() { +/// let lines = text_wrap.get_lines(text); +/// self.width = lines.max_by(|l| l.len()); +/// self.height = lines.len(); +/// return old != self; +/// } +/// } +/// let mut width = child_layout.width; +/// let mut height = child_layout.width; +/// for attr in node.attributes() { +/// match attr.name { +/// "width" => { +/// width = attr.value.as_text().unwrap().parse().unwrap(); +/// } +/// "height" => { +/// height = attr.value.as_text().unwrap().parse().unwrap(); +/// } +/// _ => unreachable!(), +/// } +/// } +/// self.width = width; +/// self.height = height; +/// old != self +/// } +/// } +/// ``` +/// The generic argument (Depstate) must be a tuple containing any number of borrowed elments that are either a [ChildDepState], [ParentDepState] or [NodeDepState]. +pub trait NodeDepState { type Ctx; - type DepState: NodeDepState; const NODE_MASK: NodeMask = NodeMask::NONE; - fn reduce(&mut self, node: NodeView, sibling: &Self::DepState, ctx: &Self::Ctx) -> bool; -} - -#[derive(Debug)] -pub struct ChildStatesChanged { - pub node_dep: Vec, - pub child_dep: Vec, -} - -#[derive(Debug)] -pub struct ParentStatesChanged { - pub node_dep: Vec, - pub parent_dep: Vec, -} - -#[derive(Debug)] -pub struct NodeStatesChanged { - pub node_dep: Vec, + fn reduce(&mut self, node: NodeView, siblings: DepState, ctx: &Self::Ctx) -> bool; } +/// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom]. pub trait State: Default + Clone { - const SIZE: usize; - - fn update_node_dep_state<'a>( - &'a mut self, - ty: MemberId, - node: &'a VNode<'a>, + #[doc(hidden)] + fn update<'a, T: Traversable>( + dirty: &[(ElementId, NodeMask)], + state_tree: &'a mut T, vdom: &'a dioxus_core::VirtualDom, ctx: &AnyMap, - ) -> Option; - /// This must be a valid resolution order. (no nodes updated before a state they rely on) - fn child_dep_types(&self, mask: &NodeMask) -> Vec; - - fn update_parent_dep_state<'a>( - &'a mut self, - ty: MemberId, - node: &'a VNode<'a>, - vdom: &'a dioxus_core::VirtualDom, - parent: Option<&Self>, - ctx: &AnyMap, - ) -> Option; - /// This must be a valid resolution order. (no nodes updated before a state they rely on) - fn parent_dep_types(&self, mask: &NodeMask) -> Vec; - - fn update_child_dep_state<'a>( - &'a mut self, - ty: MemberId, - node: &'a VNode<'a>, - vdom: &'a dioxus_core::VirtualDom, - children: &[&Self], - ctx: &AnyMap, - ) -> Option; - /// This must be a valid resolution order. (no nodes updated before a state they rely on) - fn node_dep_types(&self, mask: &NodeMask) -> Vec; + ) -> FxHashSet; } // Todo: once GATs land we can model multable dependencies @@ -159,44 +164,14 @@ impl ChildDepState for () { impl ParentDepState for () { type Ctx = (); type DepState = (); - fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &Self::Ctx) -> bool { + fn reduce<'a>(&mut self, _: NodeView, _: Option<&'a Self::DepState>, _: &Self::Ctx) -> bool { false } } -impl NodeDepState for () { +impl NodeDepState<()> for () { type Ctx = (); - type DepState = (); - fn reduce(&mut self, _: NodeView, _sibling: &Self::DepState, _: &Self::Ctx) -> bool { + fn reduce(&mut self, _: NodeView, _sibling: (), _: &Self::Ctx) -> bool { false } } - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct MemberId(pub usize); - -impl Sub for MemberId { - type Output = MemberId; - fn sub(self, rhs: usize) -> Self::Output { - MemberId(self.0 - rhs) - } -} - -impl Add for MemberId { - type Output = MemberId; - fn add(self, rhs: usize) -> Self::Output { - MemberId(self.0 + rhs) - } -} - -impl SubAssign for MemberId { - fn sub_assign(&mut self, rhs: usize) { - *self = *self - rhs; - } -} - -impl AddAssign for MemberId { - fn add_assign(&mut self, rhs: usize) { - *self = *self + rhs; - } -} diff --git a/packages/native-core/src/traversable.rs b/packages/native-core/src/traversable.rs new file mode 100644 index 000000000..d09227573 --- /dev/null +++ b/packages/native-core/src/traversable.rs @@ -0,0 +1,100 @@ +pub trait Traversable { + type Id: Copy; + type Node; + + fn height(&self, id: Self::Id) -> Option; + + fn get(&self, id: Self::Id) -> Option<&Self::Node>; + fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node>; + + fn children(&self, node: Self::Id) -> &[Self::Id]; + fn parent(&self, node: Self::Id) -> Option; + + fn map &N, FMut: Fn(&mut Self::Node) -> &mut N>( + &mut self, + f: F, + f_mut: FMut, + ) -> Map + where + Self: Sized, + { + Map { + tree: self, + f, + f_mut, + } + } + + // this is safe because no node will have itself as it's parent + fn get_node_parent_mut( + &mut self, + id: Self::Id, + ) -> (Option<&mut Self::Node>, Option<&mut Self::Node>) { + let node = self.get_mut(id).map(|n| n as *mut _); + let parent = self + .parent(id) + .and_then(|n| self.get_mut(n)) + .map(|n| n as *mut _); + unsafe { (node.map(|n| &mut *n), parent.map(|n| &mut *n)) } + } + + // this is safe because no node will have itself as a child + fn get_node_children_mut( + &mut self, + id: Self::Id, + ) -> (Option<&mut Self::Node>, Vec<&mut Self::Node>) { + let node = self.get_mut(id).map(|n| n as *mut _); + let mut children = Vec::new(); + let children_indexes = self.children(id).to_vec(); + for id in children_indexes { + if let Some(n) = self.get_mut(id) { + children.push(unsafe { &mut *(n as *mut _) }); + } + } + unsafe { (node.map(|n| &mut *n), children) } + } +} + +pub struct Map< + 'a, + T: Traversable, + N, + F: Fn(&::Node) -> &N, + FMut: Fn(&mut ::Node) -> &mut N, +> { + f: F, + f_mut: FMut, + tree: &'a mut T, +} + +impl< + 'a, + T: Traversable, + N, + F: Fn(&::Node) -> &N, + FMut: Fn(&mut ::Node) -> &mut N, + > Traversable for Map<'a, T, N, F, FMut> +{ + type Id = ::Id; + type Node = N; + + fn height(&self, id: Self::Id) -> Option { + self.tree.height(id) + } + + fn get(&self, id: Self::Id) -> Option<&Self::Node> { + self.tree.get(id).map(&self.f) + } + + fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node> { + self.tree.get_mut(id).map(&self.f_mut) + } + + fn children(&self, id: Self::Id) -> &[Self::Id] { + self.tree.children(id) + } + + fn parent(&self, id: Self::Id) -> Option { + self.tree.parent(id) + } +} diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index c2806d5e7..93ea0e64d 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -62,13 +62,12 @@ pub(crate) struct Focus { pub level: FocusLevel, } -impl NodeDepState for Focus { +impl NodeDepState<()> for Focus { type Ctx = (); - type DepState = (); const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners(); - fn reduce(&mut self, node: NodeView<'_>, _sibling: &Self::DepState, _: &Self::Ctx) -> bool { + fn reduce(&mut self, node: NodeView<'_>, _sibling: (), _: &Self::Ctx) -> bool { let new = Focus { level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") { if let Some(index) = a diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 59aa7a7e3..250f15a25 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -253,7 +253,7 @@ impl InnerInputState { let mut parent = node.parent; while let Some(parent_id) = parent { will_bubble.insert(parent_id); - parent = dom[parent_id.0].parent; + parent = dom[parent_id].parent; } resolved_events.push(UserEvent { scope_id: None, diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index bceb4e7ef..ff5cf304e 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -94,7 +94,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { let to_update = rdom.apply_mutations(vec![mutations]); let mut any_map = AnyMap::new(); any_map.insert(taffy.clone()); - let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap(); + let _to_rerender = rdom.update_state(&dom, to_update, any_map); } render_vdom( @@ -133,7 +133,8 @@ fn render_vdom( terminal.clear().unwrap(); } - let mut to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); + let mut to_rerender: fxhash::FxHashSet = + vec![ElementId(0)].into_iter().collect(); let mut updated = true; loop { @@ -153,7 +154,7 @@ fn render_vdom( fn resize(dims: Rect, taffy: &mut Taffy, rdom: &Dom) { let width = dims.width; let height = dims.height; - let root_node = rdom[0].state.layout.node.unwrap(); + let root_node = rdom[ElementId(0)].state.layout.node.unwrap(); taffy .compute_layout( @@ -170,7 +171,7 @@ fn render_vdom( let rdom = rdom.borrow(); // size is guaranteed to not change when rendering resize(frame.size(), &mut taffy.borrow_mut(), &rdom); - let root = &rdom[0]; + let root = &rdom[ElementId(0)]; render::render_vnode( frame, &taffy.borrow(), @@ -249,7 +250,7 @@ fn render_vdom( // update the style and layout let mut any_map = AnyMap::new(); any_map.insert(taffy.clone()); - to_rerender = rdom.update_state(vdom, to_update, any_map).unwrap(); + to_rerender = rdom.update_state(vdom, to_update, any_map); } } diff --git a/packages/tui/src/node.rs b/packages/tui/src/node.rs index 9cf4ba6d2..be0672c23 100644 --- a/packages/tui/src/node.rs +++ b/packages/tui/src/node.rs @@ -45,11 +45,9 @@ impl Default for PreventDefault { } } -impl NodeDepState for PreventDefault { +impl NodeDepState<()> for PreventDefault { type Ctx = (); - type DepState = (); - const NODE_MASK: dioxus_native_core::node_ref::NodeMask = dioxus_native_core::node_ref::NodeMask::new_with_attrs( dioxus_native_core::node_ref::AttributeMask::Static(&sorted_str_slice!([ @@ -60,7 +58,7 @@ impl NodeDepState for PreventDefault { fn reduce( &mut self, node: dioxus_native_core::node_ref::NodeView, - _sibling: &Self::DepState, + _sibling: (), _ctx: &Self::Ctx, ) -> bool { let new = match node diff --git a/packages/tui/src/query.rs b/packages/tui/src/query.rs index 495a80539..7a850df42 100644 --- a/packages/tui/src/query.rs +++ b/packages/tui/src/query.rs @@ -15,7 +15,7 @@ use crate::Dom; /// Allows querying the layout of nodes after rendering. It will only provide a correct value after a node is rendered. /// Provided as a root context for all tui applictions. /// # Example -/// ```rust +/// ```rust, ignore /// use dioxus::prelude::*; /// use dioxus::tui::query::Query; /// use dioxus::tui::Size; diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 00717029f..9a0c5e755 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -78,7 +78,7 @@ pub(crate) fn render_vnode( } for c in children { - render_vnode(frame, layout, rdom, &rdom[c.0], cfg, location); + render_vnode(frame, layout, rdom, &rdom[*c], cfg, location); } } NodeType::Placeholder => unreachable!(),