From 92010b0babbb59b99e683929d317200ecd1e6a40 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 12 Apr 2022 07:51:57 -0500 Subject: [PATCH] WIP update tests --- packages/native-core-macro/src/lib.rs | 84 ++++-- packages/native-core/src/lib.rs | 1 + packages/native-core/src/real_dom.rs | 274 +++++++++++++------ packages/native-core/src/real_dom_new_api.rs | 213 +++++++++++--- packages/native-core/tests/change_nodes.rs | 9 +- packages/native-core/tests/initial_build.rs | 9 +- packages/native-core/tests/parse.rs | 14 +- packages/native-core/tests/state.rs | 229 ++++++++++------ packages/tui/Cargo.toml | 1 + packages/tui/src/lib.rs | 7 +- 10 files changed, 597 insertions(+), 244 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 740c214ba..5ec2fceba 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -5,7 +5,9 @@ use quote::{quote, ToTokens}; use syn::{ self, parse::{Parse, ParseStream}, - Field, Ident, Token, Type, + punctuated::Punctuated, + token::Paren, + Field, Ident, Token, Type, TypeTuple, }; #[derive(PartialEq)] @@ -103,8 +105,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let gen = quote! { impl State for #type_name{ - fn update_node_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VElement<'a>, ctx: &anymap::AnyMap) -> bool{ - use dioxus_native_core::real_dom_new_api::NodeDepState; + fn update_node_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: Option<&'a dioxus_core::VElement<'a>>, ctx: &anymap::AnyMap) -> bool{ + use dioxus_native_core::real_dom_new_api::NodeDepState as _; + // println!("called update_node_dep_state with ty: {:?}", ty); if false { unreachable!(); } @@ -114,8 +117,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { } } - fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VElement<'a>, parent: &Self, ctx: &anymap::AnyMap) -> bool{ - use dioxus_native_core::real_dom_new_api::ParentDepState; + fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: Option<&'a dioxus_core::VElement<'a>>, parent: Option<&Self>, ctx: &anymap::AnyMap) -> bool{ + use dioxus_native_core::real_dom_new_api::ParentDepState as _; + // println!("called update_parent_dep_state with ty: {:?}", ty); if false { unreachable!(); } @@ -125,8 +129,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { } } - fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VElement<'a>, children: Vec<&Self>, ctx: &anymap::AnyMap) -> bool{ - use dioxus_native_core::real_dom_new_api::ChildDepState; + fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: Option<&'a dioxus_core::VElement<'a>>, children: &[&Self], ctx: &anymap::AnyMap) -> bool{ + use dioxus_native_core::real_dom_new_api::ChildDepState as _; + // println!("called update_child_dep_state with ty: {:?}", ty); if false { unreachable!() } @@ -200,12 +205,34 @@ struct DepTypes { } impl Parse for DepTypes { - fn parse(input: ParseStream) -> Result { - let ctx_ty = input.parse::().ok(); - let comma = input.parse::().ok(); - let dep_ty = input.parse::().ok(); - let dep_ty = comma.and(dep_ty); - Ok(DepTypes { ctx_ty, dep_ty }) + fn parse(input: ParseStream) -> Result { + let dep_ty = input.parse().ok(); + let comma: Option = input.parse().ok(); + let ctx_ty = input.parse().ok(); + Ok(Self { + ctx_ty: comma.and(ctx_ty), + dep_ty, + }) + } +} + +struct NodeDepTypes { + ctx_ty: Option, +} + +impl Parse for NodeDepTypes { + fn parse(input: ParseStream) -> Result { + let ctx_ty = input.parse().ok(); + Ok(Self { ctx_ty }) + } +} + +impl From for DepTypes { + fn from(node_dep_types: NodeDepTypes) -> Self { + Self { + ctx_ty: node_dep_types.ctx_ty, + dep_ty: None, + } } } @@ -243,7 +270,10 @@ impl<'a> StateMember<'a> { _ => None, }) .flatten()?; - let deps: DepTypes = a.parse_args().ok()?; + let deps: DepTypes = match dep_kind { + DepKind::NodeDepState => a.parse_args::().ok()?.into(), + _ => a.parse_args().ok()?, + }; Some(Self { mem, @@ -260,8 +290,19 @@ impl<'a> StateMember<'a> { fn reduce_self(&self) -> quote::__private::TokenStream { let ident = &self.mem.ident; let get_ctx = if let Some(ctx_ty) = &self.ctx_ty { - let msg = ctx_ty.to_token_stream().to_string() + " not found in context"; - quote! {ctx.get().expect(#msg)} + if ctx_ty + == &Type::Tuple(TypeTuple { + paren_token: Paren { + span: quote::__private::Span::call_site(), + }, + elems: Punctuated::new(), + }) + { + quote! {&()} + } else { + let msg = ctx_ty.to_token_stream().to_string() + " not found in context"; + quote! {ctx.get().expect(#msg)} + } } else { quote! {&()} }; @@ -276,7 +317,7 @@ impl<'a> StateMember<'a> { quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident).collect(), #get_ctx)) } DepKind::ParentDepState => { - quote!(self.#ident.reduce(#node_view, &parent.#dep_ident, #get_ctx)) + quote!(self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx)) } } } else { @@ -288,7 +329,7 @@ impl<'a> StateMember<'a> { quote!(self.#ident.reduce(#node_view, &(), #get_ctx)) } DepKind::ParentDepState => { - quote!(self.#ident.reduce(#node_view, &(), #get_ctx)) + quote!(self.#ident.reduce(#node_view, Some(&()), #get_ctx)) } } } @@ -296,6 +337,11 @@ impl<'a> StateMember<'a> { fn type_id(&self) -> quote::__private::TokenStream { let ty = &self.mem.ty; - quote!(std::any::TypeId::of::<#ty>()) + // quote!(std::any::TypeId::of::<#ty>()) + quote!({ + let type_id = std::any::TypeId::of::<#ty>(); + // println!("{:?}", type_id); + type_id + }) } } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 0ad573834..580380f68 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,3 +1,4 @@ pub mod layout_attributes; pub mod real_dom; pub mod real_dom_new_api; +pub use dioxus_native_core_macro; diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index f40aaf474..55ae7e0a8 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,13 +1,14 @@ use anymap::AnyMap; use fxhash::{FxHashMap, FxHashSet}; use std::{ + any::TypeId, collections::VecDeque, ops::{Index, IndexMut}, }; use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; -use crate::real_dom_new_api::{State, NodeMask}; +use crate::real_dom_new_api::{AttributeMask, NodeMask, State}; /// 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. @@ -47,7 +48,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 { + pub fn apply_mutations(&mut self, mutations_vec: Vec) -> Vec<(usize, NodeMask)> { let mut nodes_updated = Vec::new(); for mutations in mutations_vec { for e in mutations.edits { @@ -69,7 +70,7 @@ impl RealDom { .collect(); for ns in drained { self.link_child(ns, target).unwrap(); - nodes_updated.push(ns); + nodes_updated.push((ns, NodeMask::ALL)); } } ReplaceWith { root, m } => { @@ -77,7 +78,7 @@ impl RealDom { 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); + nodes_updated.push((ns, NodeMask::ALL)); self.link_child(ns, target).unwrap(); } } @@ -85,7 +86,7 @@ impl RealDom { let target = self[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); + nodes_updated.push((ns, NodeMask::ALL)); self.link_child(ns, target).unwrap(); } } @@ -93,13 +94,13 @@ impl RealDom { let target = self[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); + nodes_updated.push((ns, NodeMask::ALL)); self.link_child(ns, target).unwrap(); } } Remove { root } => { if let Some(parent) = self[root as usize].parent { - nodes_updated.push(parent.0); + nodes_updated.push((parent.0, NodeMask::NONE)); } self.remove(root as usize).unwrap(); } @@ -165,7 +166,7 @@ impl RealDom { text: new_text, } => { let target = &mut self[root as usize]; - nodes_updated.push(root as usize); + nodes_updated.push((root as usize, NodeMask::NONE)); match &mut target.node_type { NodeType::Text { text } => { *text = new_text.to_string(); @@ -173,11 +174,19 @@ impl RealDom { _ => unreachable!(), } } - SetAttribute { root, .. } => { - nodes_updated.push(root as usize); + SetAttribute { root, field, .. } => { + nodes_updated.push(( + root as usize, + NodeMask::new(AttributeMask::single(field), false, false), + )); } - RemoveAttribute { root, .. } => { - nodes_updated.push(root as usize); + RemoveAttribute { + root, name: field, .. + } => { + nodes_updated.push(( + root as usize, + NodeMask::new(AttributeMask::single(field), false, false), + )); } PopRoot {} => { self.node_stack.pop(); @@ -193,77 +202,148 @@ impl RealDom { pub fn update_state( &mut self, vdom: &VirtualDom, - nodes_updated: Vec, + nodes_updated: Vec<(usize, NodeMask)>, ctx: AnyMap, ) -> Option> { + #[derive(PartialEq, Clone, Debug)] + enum StatesToCheck { + All, + Some(Vec), + } + let mut to_rerender = FxHashSet::default(); - to_rerender.extend(nodes_updated.iter()); + to_rerender.extend(nodes_updated.iter().map(|(id, _)| id)); let mut nodes_updated: Vec<_> = nodes_updated .into_iter() - .map(|id| (id, self[id].height)) + .map(|(id, mask)| (id, self[id].height, mask, StatesToCheck::All)) .collect(); // Sort nodes first by height, then if the height is the same id. nodes_updated.sort_by(|fst, snd| fst.1.cmp(&snd.1).then(fst.0.cmp(&snd.0))); - nodes_updated.dedup(); + { + // Combine mutations that affect the same node. + let current_key = None; + for i in 0..nodes_updated.len() { + let current = nodes_updated; + } + } + println!("{:?}", nodes_updated); + + // update the state that only depends on nodes. The order does not matter. + for (id, _height, mask, to_check) in &nodes_updated { + let mut changed = false; + let node = &mut self[*id as usize]; + let ids = match to_check { + StatesToCheck::All => node.state.node_dep_types(&mask), + StatesToCheck::Some(_) => unreachable!(), + }; + for ty in ids { + let node = &mut self[*id as usize]; + let el = if let &VNode::Element(e) = node.element(vdom) { + Some(e) + } else { + None + }; + changed |= node.state.update_node_dep_state(ty, el, &ctx); + } + if changed { + to_rerender.insert(*id); + } + } // bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up. let mut to_bubble: VecDeque<_> = nodes_updated.clone().into(); - while let Some((id, height)) = to_bubble.pop_back() { - let node = &mut self[id as usize]; - let vnode = node.element(vdom); - let node_type = &node.node_type; - if let - NodeType::Element { children, tag, namespace } =node.node_type{ - + while let Some((id, height, mask, to_check)) = to_bubble.pop_back() { + let (node, children) = self.get_node_children_mut(id).unwrap(); + let children_state: Vec<_> = children.iter().map(|c| &c.state).collect(); + let ids = match to_check { + StatesToCheck::All => node.state.child_dep_types(&mask), + StatesToCheck::Some(ids) => ids, + }; + let mut changed = Vec::new(); + for ty in ids { + let el = if let &VNode::Element(e) = node.element(vdom) { + Some(e) + } else { + None + }; + if node + .state + .update_child_dep_state(ty, el, &children_state, &ctx) + { + changed.push(ty); } - let mask = NodeMask::new(attritutes, tag, namespace) - // todo: reduce cloning state - for id in node.state.child_dep_types(mask) {} - if new != old { - to_rerender.insert(id); - if let Some(p) = parent { - let i = to_bubble.partition_point(|(other_id, h)| { - *h < height - 1 || (*h == height - 1 && *other_id < p.0) + } + if let Some(parent_id) = node.parent { + if !changed.is_empty() { + to_rerender.insert(id); + let i = to_bubble.partition_point(|(other_id, 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].0 != p.0 { - to_bubble.insert(i, (p.0, height - 1)); + if i >= to_bubble.len() || to_bubble[i].0 != parent_id.0 { + to_bubble.insert( + i, + ( + parent_id.0, + height - 1, + NodeMask::NONE, + StatesToCheck::Some(changed), + ), + ); } } - let node = &mut self[id as usize]; - node.up_state = new; } } // push down state. To avoid calling reduce more times than nessisary start from the top and go down. let mut to_push: VecDeque<_> = nodes_updated.clone().into(); - while let Some((id, height)) = to_push.pop_front() { - let node = &self[id as usize]; - // todo: reduce cloning state - let old = node.down_state.clone(); - let mut new = node.down_state.clone(); - let vnode = node.element(vdom); - new.reduce( - node.parent - .filter(|e| e.0 != 0) - .map(|e| &self[e].down_state), - vnode, - ds_ctx, - ); - if new != old { - to_rerender.insert(id); - let node = &mut self[id as usize]; + while let Some((id, height, mask, to_check)) = to_push.pop_front() { + let node = &self[id]; + let ids = match to_check { + StatesToCheck::All => node.state.parent_dep_types(&mask), + StatesToCheck::Some(ids) => ids, + }; + let mut changed = Vec::new(); + let (node, parent) = self.get_node_parent_mut(id).unwrap(); + for ty in ids { + let el = if let &VNode::Element(e) = node.element(vdom) { + Some(e) + } else { + None + }; + let parent = parent.as_deref(); + let state = &mut node.state; + if state.update_parent_dep_state( + ty, + el, + parent.filter(|n| n.id.0 != 0).map(|n| &n.state), + &ctx, + ) { + changed.push(ty); + } + } + + 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(|(other_id, h)| { + let i = to_push.partition_point(|(other_id, h, ..)| { *h < height + 1 || (*h == height + 1 && *other_id < c.0) }); if i >= to_push.len() || to_push[i].0 != c.0 { - to_push.insert(i, (c.0, height + 1)); + to_push.insert( + i, + ( + c.0, + height + 1, + NodeMask::NONE, + StatesToCheck::Some(changed.clone()), + ), + ); } } } - node.down_state = new; } } @@ -333,6 +413,56 @@ impl RealDom { 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<'a>( + &'a mut self, + id: usize, + ) -> Option<(&'a mut Node, Vec<&'a 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(), + }; + return Some((node, children)); + } else { + None + } + } + } + } + + // this is safe because no node will have itself as a parent + pub fn get_node_parent_mut<'a>( + &'a mut self, + id: usize, + ) -> Option<(&'a mut Node, Option<&'a 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()); + return 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(); @@ -528,39 +658,3 @@ impl Node { self.parent = Some(parent); } } - -/// This state that is passed down to children. For example text properties (`` `` ``) would be passed to children. -/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified. -/// Called at most once per update. -pub trait PushedDownState: Default + PartialEq + Clone { - /// The context is passed to the [PushedDownState::reduce] when it is pushed down. - /// This is sometimes nessisary for lifetime purposes. - type Ctx; - fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx); -} -impl PushedDownState for () { - type Ctx = (); - fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {} -} - -/// This state is derived from children. For example a node's size could be derived from the size of children. -/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed. -/// Called at most once per update. -pub trait BubbledUpState: Default + PartialEq + Clone { - /// The context is passed to the [BubbledUpState::reduce] when it is bubbled up. - /// This is sometimes nessisary for lifetime purposes. - type Ctx; - fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a; -} -impl BubbledUpState for () { - type Ctx = (); - fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { - } -} diff --git a/packages/native-core/src/real_dom_new_api.rs b/packages/native-core/src/real_dom_new_api.rs index 9cc5d0550..042e625c1 100644 --- a/packages/native-core/src/real_dom_new_api.rs +++ b/packages/native-core/src/real_dom_new_api.rs @@ -3,12 +3,13 @@ use std::any::TypeId; use anymap::AnyMap; use dioxus_core::{Attribute, VElement}; +#[derive(Debug)] pub struct NodeView<'a> { - inner: &'a VElement<'a>, + inner: Option<&'a VElement<'a>>, view: NodeMask, } impl<'a> NodeView<'a> { - pub fn new(velement: &'a VElement<'a>, view: NodeMask) -> Self { + pub fn new(velement: Option<&'a VElement<'a>>, view: NodeMask) -> Self { Self { inner: velement, view: view, @@ -17,7 +18,7 @@ impl<'a> NodeView<'a> { pub fn tag(&self) -> Option<&'a str> { if self.view.tag { - Some(self.inner.tag) + self.inner.map(|el| el.tag) } else { None } @@ -25,7 +26,7 @@ impl<'a> NodeView<'a> { pub fn namespace(&self) -> Option<&'a str> { if self.view.namespace { - self.inner.namespace + self.inner.map(|el| el.namespace).flatten() } else { None } @@ -33,23 +34,159 @@ impl<'a> NodeView<'a> { pub fn attributes(&self) -> impl Iterator> { self.inner - .attributes + .map(|el| el.attributes) + .unwrap_or_default() .iter() - .filter(|a| self.view.attritutes.contains(&a.name)) + .filter(|a| self.view.attritutes.contains_attribute(&a.name)) } } -#[derive(Default)] +#[derive(PartialEq, Clone, Debug)] +pub enum AttributeMask { + All, + Dynamic(Vec<&'static str>), + Static(&'static [&'static str]), +} + +impl AttributeMask { + pub const NONE: Self = Self::Static(&[]); + + fn contains_attribute(&self, attr: &'static str) -> bool { + match self { + AttributeMask::All => true, + AttributeMask::Dynamic(l) => l.contains(&attr), + AttributeMask::Static(l) => l.contains(&attr), + } + } + + pub fn single(new: &'static str) -> Self { + Self::Dynamic(vec![new]) + } + + pub fn verify(&self) { + match &self { + AttributeMask::Static(attrs) => debug_assert!( + attrs.windows(2).all(|w| w[0] < w[1]), + "attritutes must be increasing" + ), + AttributeMask::Dynamic(attrs) => debug_assert!( + attrs.windows(2).all(|w| w[0] < w[1]), + "attritutes must be increasing" + ), + _ => (), + } + } + + pub fn union(&self, other: &Self) -> Self { + pub fn union_iter( + s_iter: impl Iterator, + o_iter: impl Iterator, + ) -> Vec<&'static str> { + let mut s_peekable = s_iter.peekable(); + let mut o_peekable = o_iter.peekable(); + let mut v = Vec::new(); + while let Some(s_i) = s_peekable.peek() { + loop { + if let Some(o_i) = o_peekable.peek() { + if o_i > s_i { + break; + } else { + v.push(o_peekable.next().unwrap()); + } + } else { + break; + } + } + v.push(s_peekable.next().unwrap()); + } + while let Some(o_i) = o_peekable.next() { + v.push(o_i); + } + v + } + + let new = match (self, other) { + (AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => { + AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied())) + } + (AttributeMask::Static(s), AttributeMask::Dynamic(o)) => { + AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied())) + } + (AttributeMask::Dynamic(s), AttributeMask::Static(o)) => { + AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied())) + } + (AttributeMask::Static(s), AttributeMask::Static(o)) => { + AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied())) + } + _ => AttributeMask::All, + }; + new.verify(); + new + } + + fn overlaps(&self, other: &Self) -> bool { + fn overlaps_iter( + mut self_iter: impl Iterator, + mut other_iter: impl Iterator, + ) -> bool { + if let Some(mut other_attr) = other_iter.next() { + while let Some(self_attr) = self_iter.next() { + while other_attr < self_attr { + if let Some(attr) = other_iter.next() { + other_attr = attr; + } else { + return false; + } + } + if other_attr == self_attr { + return true; + } + } + } + false + } + match (self, other) { + (AttributeMask::All, AttributeMask::All) => true, + (AttributeMask::All, AttributeMask::Dynamic(v)) => !v.is_empty(), + (AttributeMask::All, AttributeMask::Static(s)) => !s.is_empty(), + (AttributeMask::Dynamic(v), AttributeMask::All) => !v.is_empty(), + (AttributeMask::Static(s), AttributeMask::All) => !s.is_empty(), + (AttributeMask::Dynamic(v1), AttributeMask::Dynamic(v2)) => { + overlaps_iter(v1.iter().copied(), v2.iter().copied()) + } + (AttributeMask::Dynamic(v), AttributeMask::Static(s)) => { + overlaps_iter(v.iter().copied(), s.iter().copied()) + } + (AttributeMask::Static(s), AttributeMask::Dynamic(v)) => { + overlaps_iter(v.iter().copied(), s.iter().copied()) + } + (AttributeMask::Static(s1), AttributeMask::Static(s2)) => { + overlaps_iter(s1.iter().copied(), s2.iter().copied()) + } + } + } +} + +impl Default for AttributeMask { + fn default() -> Self { + AttributeMask::Static(&[]) + } +} + +#[derive(Default, PartialEq, Clone, Debug)] pub struct NodeMask { // must be sorted - attritutes: &'static [&'static str], + attritutes: AttributeMask, tag: bool, namespace: bool, } impl NodeMask { + pub const NONE: Self = Self::new(AttributeMask::Static(&[]), false, false); + pub const ALL: Self = Self::new(AttributeMask::All, true, true); + /// attritutes must be sorted! - pub const fn new(attritutes: &'static [&'static str], tag: bool, namespace: bool) -> Self { + pub const fn new(attritutes: AttributeMask, tag: bool, namespace: bool) -> Self { Self { attritutes, tag, @@ -57,13 +194,6 @@ impl NodeMask { } } - pub fn verify(&self) { - debug_assert!( - self.attritutes.windows(2).all(|w| w[0] < w[1]), - "attritutes must be increasing" - ); - } - pub fn overlaps(&self, other: &Self) -> bool { (self.tag && other.tag) || (self.namespace && other.namespace) @@ -71,51 +201,48 @@ impl NodeMask { } fn attritutes_overlap(&self, other: &Self) -> bool { - let mut self_attrs = self.attritutes.iter(); - let mut other_attrs = other.attritutes.iter(); - if let Some(mut other_attr) = other_attrs.next() { - while let Some(self_attr) = self_attrs.next() { - while other_attr < self_attr { - if let Some(attr) = other_attrs.next() { - other_attr = attr; - } else { - return false; - } - } - if other_attr == self_attr { - return true; - } - } - } - false + self.attritutes.overlaps(&other.attritutes) } } +/// This state is derived from children. For example a node's size could be derived from the size of children. +/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed. +/// Called at most once per update. pub trait ChildDepState { + /// The context is passed to the [PushedDownState::reduce] when it is pushed down. + /// This is sometimes nessisary for lifetime purposes. type Ctx; type DepState: ChildDepState; - const NODE_MASK: NodeMask = NodeMask::new(&[], false, false); + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false); fn reduce(&mut self, node: NodeView, children: Vec<&Self::DepState>, ctx: &Self::Ctx) -> bool; } +/// This state that is passed down to children. For example text properties (`` `` ``) would be passed to children. +/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified. +/// Called at most once per update. pub trait ParentDepState { + /// The context is passed to the [PushedDownState::reduce] when it is pushed down. + /// This is sometimes nessisary for lifetime purposes. type Ctx; type DepState: ParentDepState; - const NODE_MASK: NodeMask = NodeMask::new(&[], false, false); - fn reduce(&mut self, node: NodeView, parent: &Self::DepState, ctx: &Self::Ctx) -> bool; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false); + fn reduce(&mut self, node: NodeView, parent: Option<&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 parrent's [PushedDownState] is modified. +/// Called at most once per update. pub trait NodeDepState { type Ctx; - const NODE_MASK: NodeMask = NodeMask::new(&[], false, false); + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false); fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool; } -pub trait State: Default { +pub trait State: Default + Clone { fn update_node_dep_state<'a>( &'a mut self, ty: TypeId, - node: &'a VElement<'a>, + node: Option<&'a VElement<'a>>, ctx: &AnyMap, ) -> bool; /// This must be a valid resolution order. (no nodes updated before a state they rely on) @@ -124,8 +251,8 @@ pub trait State: Default { fn update_parent_dep_state<'a>( &'a mut self, ty: TypeId, - node: &'a VElement<'a>, - parent: &Self, + node: Option<&'a VElement<'a>>, + parent: Option<&Self>, ctx: &AnyMap, ) -> bool; /// This must be a valid resolution order. (no nodes updated before a state they rely on) @@ -134,8 +261,8 @@ pub trait State: Default { fn update_child_dep_state<'a>( &'a mut self, ty: TypeId, - node: &'a VElement<'a>, - children: Vec<&Self>, + node: Option<&'a VElement<'a>>, + children: &[&Self], ctx: &AnyMap, ) -> bool; /// This must be a valid resolution order. (no nodes updated before a state they rely on) diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core/tests/change_nodes.rs index 6afb225d3..bf3de4368 100644 --- a/packages/native-core/tests/change_nodes.rs +++ b/packages/native-core/tests/change_nodes.rs @@ -3,8 +3,13 @@ use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_native_core::real_dom::RealDom; +use dioxus_native_core::real_dom_new_api::State; +use dioxus_native_core_macro::State; use std::cell::Cell; +#[derive(State, Default, Clone)] +struct Empty {} + #[test] fn remove_node() { #[allow(non_snake_case)] @@ -20,7 +25,7 @@ fn remove_node() { } }); - let mut dom: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); let child_div = VElement { @@ -92,7 +97,7 @@ fn add_node() { div{} }); - let mut dom: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core/tests/initial_build.rs index df054dcdc..321820b7b 100644 --- a/packages/native-core/tests/initial_build.rs +++ b/packages/native-core/tests/initial_build.rs @@ -5,6 +5,11 @@ use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_native_core::real_dom::RealDom; +use dioxus_native_core::real_dom_new_api::State; +use dioxus_native_core_macro::State; + +#[derive(State, Default, Clone)] +struct Empty {} #[test] fn initial_build_simple() { @@ -21,7 +26,7 @@ fn initial_build_simple() { div{} }); - let mut dom: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); let root_div = VElement { @@ -60,7 +65,7 @@ fn initial_build_with_children() { } }); - let mut dom: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); let first_text = VText { diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs index 8f680bf88..a3cf69ecf 100644 --- a/packages/native-core/tests/parse.rs +++ b/packages/native-core/tests/parse.rs @@ -1,16 +1,16 @@ use dioxus_native_core::real_dom_new_api::*; use dioxus_native_core_macro::*; -#[derive(State)] +#[derive(State, Default, Clone)] struct Z { // depends on just attributes and no context #[node_dep_state()] x: A, // depends on attributes, the B component of children and i32 context - #[child_dep_state(i32, B)] + #[child_dep_state(B, i32)] y: B, // depends on attributes, the C component of it's parent and a u8 context - #[parent_dep_state(u8, C)] + #[parent_dep_state(C, u8)] z: C, } @@ -22,7 +22,7 @@ struct Z { use dioxus_native_core::real_dom_new_api::NodeDepState; -#[derive(PartialEq)] +#[derive(Default, Clone)] struct A; impl NodeDepState for A { type Ctx = (); @@ -31,7 +31,7 @@ impl NodeDepState for A { } } -#[derive(PartialEq)] +#[derive(Default, Clone)] struct B; impl ChildDepState for B { type Ctx = i32; @@ -46,7 +46,7 @@ impl ChildDepState for B { } } -#[derive(PartialEq)] +#[derive(Default, Clone)] struct C; impl ParentDepState for C { type Ctx = u8; @@ -54,7 +54,7 @@ impl ParentDepState for C { fn reduce( &mut self, _: dioxus_native_core::real_dom_new_api::NodeView, - _: &Self::DepState, + _: Option<&Self::DepState>, _: &u8, ) -> bool { todo!() diff --git a/packages/native-core/tests/state.rs b/packages/native-core/tests/state.rs index ff0dee1d5..2a17ac520 100644 --- a/packages/native-core/tests/state.rs +++ b/packages/native-core/tests/state.rs @@ -1,60 +1,125 @@ +use anymap::AnyMap; use dioxus_core::VNode; use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_native_core::real_dom::*; +use dioxus_native_core::real_dom_new_api::{ + AttributeMask, ChildDepState, NodeDepState, NodeMask, NodeView, ParentDepState, State, +}; +use dioxus_native_core_macro::State; -#[derive(Debug, Clone, PartialEq, Default)] -struct CallCounter(u32); -impl BubbledUpState for CallCounter { +#[derive(Debug, Clone, Default, State)] +struct CallCounterState { + #[child_dep_state(ChildDepCallCounter)] + child_counter: ChildDepCallCounter, + #[parent_dep_state(ParentDepCallCounter)] + parent_counter: ParentDepCallCounter, + #[node_dep_state()] + node_counter: NodeDepCallCounter, +} + +#[derive(Debug, Clone, Default)] +struct ChildDepCallCounter(u32); +impl ChildDepState for ChildDepCallCounter { type Ctx = (); - - fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { + type DepState = Self; + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce( + &mut self, + _node: NodeView, + _children: Vec<&Self::DepState>, + _ctx: &Self::Ctx, + ) -> bool { self.0 += 1; + true } } -impl PushedDownState for CallCounter { +#[derive(Debug, Clone, Default)] +struct ParentDepCallCounter(u32); +impl ParentDepState for ParentDepCallCounter { type Ctx = (); - - fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) { + type DepState = Self; + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce( + &mut self, + _node: NodeView, + _parent: Option<&Self::DepState>, + _ctx: &Self::Ctx, + ) -> bool { self.0 += 1; + true + } +} + +#[derive(Debug, Clone, Default)] +struct NodeDepCallCounter(u32); +impl NodeDepState for NodeDepCallCounter { + type Ctx = (); + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce(&mut self, _node: NodeView, _ctx: &Self::Ctx) -> bool { + self.0 += 1; + true } } #[derive(Debug, Clone, PartialEq, Default)] -struct BubbledUpStateTester(String, Vec>); -impl BubbledUpState for BubbledUpStateTester { +struct BubbledUpStateTester(Option, Vec>); +impl ChildDepState for BubbledUpStateTester { type Ctx = u32; - - fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { + type DepState = Self; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false); + fn reduce(&mut self, node: NodeView, children: Vec<&Self::DepState>, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = BubbledUpStateTester( - vnode.mounted_id().to_string(), - children.map(|c| Box::new(c.clone())).collect(), + node.tag().map(|s| s.to_string()), + children.into_iter().map(|c| Box::new(c.clone())).collect(), ); + true } } #[derive(Debug, Clone, PartialEq, Default)] -struct PushedDownStateTester(String, Option>); -impl PushedDownState for PushedDownStateTester { +struct PushedDownStateTester(Option, Option>); +impl ParentDepState for PushedDownStateTester { type Ctx = u32; - - fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx) { + type DepState = Self; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false); + fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = PushedDownStateTester( - vnode.mounted_id().to_string(), + node.tag().map(|s| s.to_string()), parent.map(|c| Box::new(c.clone())), ); + true + } +} + +#[derive(State, Clone, Default, Debug)] +struct StateTester { + #[child_dep_state(BubbledUpStateTester, u32)] + bubbled: BubbledUpStateTester, + #[parent_dep_state(PushedDownStateTester, u32)] + pushed: PushedDownStateTester, + #[node_dep_state(u32)] + node: NodeStateTester, +} + +#[derive(Debug, Clone, PartialEq, Default)] +struct NodeStateTester(Option, Vec<(String, String)>); +impl NodeDepState for NodeStateTester { + type Ctx = u32; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false); + fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool { + assert_eq!(*ctx, 42); + *self = NodeStateTester( + node.tag().map(|s| s.to_string()), + node.attributes() + .map(|a| (a.name.to_string(), a.value.to_string())) + .collect(), + ); + true } } @@ -72,71 +137,68 @@ fn state_initial() { let mutations = vdom.create_vnodes(rsx! { div { - p{} + p{ + color: "red" + } h1{} } }); - let mut dom: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut 42, &mut 42); + let mut ctx = AnyMap::new(); + ctx.insert(42u32); + let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx); let root_div = &dom[1]; - assert_eq!(root_div.up_state.0, "1"); + assert_eq!(root_div.state.bubbled.0, Some("div".to_string())); assert_eq!( - root_div.up_state.1, + root_div.state.bubbled.1, vec![ - Box::new(BubbledUpStateTester("2".to_string(), Vec::new())), - Box::new(BubbledUpStateTester("3".to_string(), Vec::new())) + Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())), + Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new())) ] ); - assert_eq!(root_div.down_state.0, "1"); - assert_eq!(root_div.down_state.1, None); + assert_eq!(root_div.state.pushed.0, Some("div".to_string())); + assert_eq!(root_div.state.pushed.1, None); + assert_eq!(root_div.state.node.0, Some("div".to_string())); + assert_eq!(root_div.state.node.1, vec![]); let child_p = &dom[2]; - assert_eq!(child_p.up_state.0, "2"); - assert_eq!(child_p.up_state.1, Vec::new()); - assert_eq!(child_p.down_state.0, "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())); assert_eq!( - child_p.down_state.1, - Some(Box::new(PushedDownStateTester("1".to_string(), None))) + child_p.state.pushed.1, + Some(Box::new(PushedDownStateTester( + Some("div".to_string()), + None + ))) + ); + assert_eq!(child_p.state.node.0, Some("p".to_string())); + assert_eq!( + child_p.state.node.1, + vec![("color".to_string(), "red".to_string())] ); let child_h1 = &dom[3]; - assert_eq!(child_h1.up_state.0, "3"); - assert_eq!(child_h1.up_state.1, Vec::new()); - assert_eq!(child_h1.down_state.0, "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())); assert_eq!( - child_h1.down_state.1, - Some(Box::new(PushedDownStateTester("1".to_string(), None))) + child_h1.state.pushed.1, + Some(Box::new(PushedDownStateTester( + Some("div".to_string()), + None + ))) ); + assert_eq!(child_h1.state.node.0, Some("h1".to_string())); + assert_eq!(child_h1.state.node.1, vec![]); } #[test] fn state_reduce_initally_called_minimally() { - #[derive(Debug, Clone, PartialEq, Default)] - struct CallCounter(u32); - impl BubbledUpState for CallCounter { - type Ctx = (); - - fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { - self.0 += 1; - } - } - - impl PushedDownState for CallCounter { - type Ctx = (); - - fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) { - self.0 += 1; - } - } - #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { @@ -178,14 +240,15 @@ fn state_reduce_initally_called_minimally() { } }); - let mut dom: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { - assert_eq!(n.up_state.0, 1); - assert_eq!(n.down_state.0, 1); + assert_eq!(n.state.child_counter.0, 1); + assert_eq!(n.state.parent_counter.0, 1); + assert_eq!(n.state.node_counter.0, 1); }); } @@ -194,6 +257,7 @@ fn state_reduce_down_called_minimally_on_update() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { + width: "100%", div{ div{ p{} @@ -233,10 +297,10 @@ fn state_reduce_down_called_minimally_on_update() { } }); - let mut dom: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); let nodes_updated = dom.apply_mutations(vec![Mutations { edits: vec![DomEdit::SetAttribute { root: 1, @@ -247,11 +311,13 @@ fn state_reduce_down_called_minimally_on_update() { dirty_scopes: fxhash::FxHashSet::default(), refs: Vec::new(), }]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { - assert_eq!(n.down_state.0, 2); + println!("{:?}", n.state); + // assert_eq!(n.state.parent_counter.0, 2); }); + panic!() } #[test] @@ -259,6 +325,7 @@ fn state_reduce_up_called_minimally_on_update() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { + width: "100%", div{ div{ p{} @@ -298,10 +365,10 @@ fn state_reduce_up_called_minimally_on_update() { } }); - let mut dom: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); let nodes_updated = dom.apply_mutations(vec![Mutations { edits: vec![DomEdit::SetAttribute { root: 4, @@ -312,9 +379,11 @@ fn state_reduce_up_called_minimally_on_update() { dirty_scopes: fxhash::FxHashSet::default(), refs: Vec::new(), }]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { - assert_eq!(n.up_state.0, if n.id.0 > 4 { 1 } else { 2 }); + println!("{:?}", n.state); + // assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 }); }); + panic!() } diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 6a65af607..43064bb63 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -25,3 +25,4 @@ futures = "0.3.19" stretch2 = { git = "https://github.com/DioxusLabs/stretch" } smallvec = "1.6" fxhash = "0.2" +anymap = "0.12.1" \ No newline at end of file diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 03e137111..4aa9b1313 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -6,7 +6,9 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::real_dom::RealDom; +use dioxus_native_core::{ + dioxus_native_core_macro::State, real_dom::RealDom, real_dom_new_api::State, +}; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, pin_mut, StreamExt, @@ -29,6 +31,9 @@ pub use config::*; pub use hooks::*; pub use render::*; +#[derive(Debug, Clone, State, Default)] +struct NodeState {} + #[derive(Clone)] pub struct TuiContext { tx: UnboundedSender,