From 92f48169e986ff0c239ad09299902c9440dc89f6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 8 Apr 2022 21:17:37 -0500 Subject: [PATCH 1/7] WIP new api --- Cargo.toml | 3 +- packages/native-core-macro/Cargo.toml | 12 + packages/native-core-macro/src/lib.rs | 285 +++++++++++++++++++ packages/native-core/Cargo.toml | 2 + packages/native-core/src/lib.rs | 1 + packages/native-core/src/real_dom_new_api.rs | 58 ++++ packages/native-core/tests/parse.rs | 62 ++++ 7 files changed, 422 insertions(+), 1 deletion(-) create mode 100644 packages/native-core-macro/Cargo.toml create mode 100644 packages/native-core-macro/src/lib.rs create mode 100644 packages/native-core/src/real_dom_new_api.rs create mode 100644 packages/native-core/tests/parse.rs diff --git a/Cargo.toml b/Cargo.toml index 95900a1d2..a1e16bada 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true } dioxus-liveview = { path = "./packages/liveview", optional = true } dioxus-native-core = { path = "./packages/native-core", optional = true } +dioxus-native-core-macro = { path = "./packages/native-core-macro", optional = true } # dioxus-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true } # dioxus-rsx = { path = "./packages/rsx", optional = true } @@ -47,7 +48,7 @@ ayatana = ["dioxus-desktop/ayatana"] router = ["dioxus-router"] tui = ["dioxus-tui"] liveview = ["dioxus-liveview"] -native-core = ["dioxus-native-core"] +native-core = ["dioxus-native-core", "dioxus-native-core-macro"] [workspace] diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml new file mode 100644 index 000000000..d71b03246 --- /dev/null +++ b/packages/native-core-macro/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dioxus-native-core-macro" +version = "0.2.0" +edition = "2021" + +[lib] +proc-macro = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +syn = { version = "1.0.11", features = ["extra-traits"] } +quote = "1.0" diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs new file mode 100644 index 000000000..883c12b2e --- /dev/null +++ b/packages/native-core-macro/src/lib.rs @@ -0,0 +1,285 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + self, + parse::{Parse, ParseStream}, + Field, Ident, Token, Type, +}; + +#[derive(PartialEq)] +enum DepKind { + NodeDepState, + ChildDepState, + ParentDepState, +} + +// macro that streams data from the State for any attributes that end with _ +#[proc_macro_derive(State, attributes(node_dep_state, child_dep_state, parent_dep_state))] +pub fn state_macro_derive(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + impl_derive_macro(&ast) +} + +fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { + let type_name = &ast.ident; + let fields: Vec<_> = match &ast.data { + syn::Data::Struct(data) => match &data.fields { + syn::Fields::Named(e) => &e.named, + syn::Fields::Unnamed(_) => todo!("unnamed fields"), + syn::Fields::Unit => todo!("unit fields"), + } + .iter() + .collect(), + _ => unimplemented!(), + }; + let strct = Struct::parse(&fields); + let state_strct = StateStruct::parse(&fields, &strct); + let node_dep_state_fields = quote::__private::TokenStream::from_iter( + state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::NodeDepState) + .map(|f| { + let ty_id = &f.type_id(); + let reduce = &f.reduce_self(); + quote! { + else if ty == #ty_id { + #reduce + } + } + }), + ); + let child_dep_state_fields = quote::__private::TokenStream::from_iter( + state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ChildDepState) + .map(|f| { + let ty_id = &f.type_id(); + let reduce = &f.reduce_self(); + quote! { + else if ty == #ty_id { + #reduce + } + } + }), + ); + let parent_dep_state_fields = quote::__private::TokenStream::from_iter( + state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ParentDepState) + .map(|f| { + let ty_id = &f.type_id(); + let reduce = &f.reduce_self(); + quote! { + else if ty == #ty_id { + #reduce + } + } + }), + ); + + let node_types = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::NodeDepState) + .map(|f| f.type_id()); + let child_types = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ChildDepState) + .map(|f| f.type_id()); + let parent_types = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ParentDepState) + .map(|f| f.type_id()); + + let type_name_str = type_name.to_string(); + + let gen = quote! { + impl State for #type_name{ + fn update_node_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, ctx: &anymap::AnyMap){ + use dioxus_native_core::real_dom_new_api::NodeDepState; + if false {} + #node_dep_state_fields + else{ + panic!("{:?} not in {}", ty, #type_name_str); + } + } + + fn update_parent_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, parent: &Self, ctx: &anymap::AnyMap){ + use dioxus_native_core::real_dom_new_api::ParentDepState; + if false {} + #parent_dep_state_fields + else{ + panic!("{:?} not in {}", ty, #type_name_str); + } + } + + fn update_child_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, children: Vec<&Self>, ctx: &anymap::AnyMap){ + use dioxus_native_core::real_dom_new_api::ChildDepState; + if false {} + #child_dep_state_fields + else{ + panic!("{:?} not in {}", ty, #type_name_str); + } + } + + fn child_dep_types(&self) -> Vec{ + // todo: order should depend on order of dependencies + vec![ + #(#child_types,)* + ] + } + + fn parent_dep_types(&self) -> Vec{ + // todo: order should depend on order of dependencies + vec![ + #(#parent_types,)* + ] + } + + fn node_dep_types(&self) -> Vec{ + vec![ + #(#node_types,)* + ] + } + } + }; + gen.into() +} + +struct Struct { + members: Vec, +} + +impl Struct { + fn parse(fields: &[&Field]) -> Self { + let members = fields.iter().filter_map(|f| Member::parse(f)).collect(); + Self { members } + } +} + +struct StateStruct<'a> { + state_members: Vec>, +} + +impl<'a> StateStruct<'a> { + fn parse(fields: &[&'a Field], strct: &'a Struct) -> Self { + let state_members = strct + .members + .iter() + .zip(fields.iter()) + .filter_map(|(m, f)| StateMember::parse(f, m, &strct)) + .collect(); + Self { state_members } + } +} + +struct DepTypes { + ctx_ty: Option, + dep_ty: Option, +} + +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 }) + } +} + +struct Member { + ty: Type, + ident: Ident, +} + +impl Member { + fn parse(field: &Field) -> Option { + Some(Self { + ty: field.ty.clone(), + ident: field.ident.as_ref()?.clone(), + }) + } +} + +struct StateMember<'a> { + mem: &'a Member, + dep_kind: DepKind, + dep_mem: Option<&'a Member>, + ctx_ty: Option, +} + +impl<'a> StateMember<'a> { + fn parse(field: &Field, mem: &'a Member, parent: &'a Struct) -> Option> { + field.attrs.iter().find_map(|a| { + let dep_kind = a + .path + .get_ident() + .map(|i| match i.to_string().as_str() { + "node_dep_state" => Some(DepKind::NodeDepState), + "child_dep_state" => Some(DepKind::ChildDepState), + "parent_dep_state" => Some(DepKind::ParentDepState), + _ => None, + }) + .flatten()?; + let deps: DepTypes = a.parse_args().ok()?; + + Some(Self { + mem, + dep_kind, + dep_mem: deps + .dep_ty + .map(|ty| parent.members.iter().find(|m| m.ty == ty)) + .flatten(), + ctx_ty: deps.ctx_ty, + }) + }) + } + + 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)} + } else { + quote! {&()} + }; + if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) { + match self.dep_kind { + DepKind::NodeDepState => { + quote!(self.#ident.reduce(node, #get_ctx);) + } + DepKind::ChildDepState => { + quote!(self.#ident.reduce(node, children.iter().map(|s| &s.#dep_ident).collect(), #get_ctx);) + } + DepKind::ParentDepState => { + quote!(self.#ident.reduce(node, &parent.#dep_ident, #get_ctx);) + } + } + } else { + match self.dep_kind { + DepKind::NodeDepState => { + quote!(self.#ident.reduce(node, #get_ctx);) + } + DepKind::ChildDepState => { + quote!(self.#ident.reduce(node, &(), #get_ctx);) + } + DepKind::ParentDepState => { + quote!(self.#ident.reduce(node, &(), #get_ctx);) + } + } + } + } + + fn type_id(&self) -> quote::__private::TokenStream { + let ty = &self.mem.ty; + quote!(std::any::TypeId::of::<#ty>()) + } +} diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 83e403487..3e37d7e8a 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -10,10 +10,12 @@ homepage = "https://dioxuslabs.com" dioxus-core = { path = "../core", version = "^0.2.0" } dioxus-html = { path = "../html", version = "^0.2.0" } dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" } +dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" } stretch2 = { git = "https://github.com/DioxusLabs/stretch" } smallvec = "1.6" fxhash = "0.2" +anymap = "0.12.1" [dev-dependencies] rand = "0.8.5" \ No newline at end of file diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 26c629446..0ad573834 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,2 +1,3 @@ pub mod layout_attributes; pub mod real_dom; +pub mod real_dom_new_api; diff --git a/packages/native-core/src/real_dom_new_api.rs b/packages/native-core/src/real_dom_new_api.rs new file mode 100644 index 000000000..7740a97d4 --- /dev/null +++ b/packages/native-core/src/real_dom_new_api.rs @@ -0,0 +1,58 @@ +use std::any::TypeId; + +use anymap::AnyMap; +use dioxus_core::{Attribute, VElement}; + +#[repr(transparent)] +pub struct NodeRef<'a>(&'a VElement<'a>); +impl<'a> NodeRef<'a> { + pub fn new(velement: &'a VElement<'a>) -> Self { + Self(velement) + } + + pub fn tag(&self) -> &'a str { + self.0.tag + } + + pub fn namespace(&self) -> Option<&'a str> { + self.0.namespace + } + + pub fn attributes(&self) -> &'a [Attribute<'a>] { + self.0.attributes + } +} + +pub trait ChildDepState: PartialEq { + type Ctx; + type DepState: ChildDepState; + fn reduce(&mut self, node: NodeRef, children: Vec<&Self::DepState>, ctx: &Self::Ctx); +} + +pub trait ParentDepState: PartialEq { + type Ctx; + type DepState: ParentDepState; + fn reduce(&mut self, node: NodeRef, parent: &Self::DepState, ctx: &Self::Ctx); +} + +pub trait NodeDepState: PartialEq { + type Ctx; + fn reduce(&mut self, node: NodeRef, ctx: &Self::Ctx); +} + +pub trait State { + fn update_node_dep_state(&mut self, ty: TypeId, node: NodeRef, ctx: &AnyMap); + fn child_dep_types(&self) -> Vec; + + fn update_parent_dep_state(&mut self, ty: TypeId, node: NodeRef, parent: &Self, ctx: &AnyMap); + fn parent_dep_types(&self) -> Vec; + + fn update_child_dep_state( + &mut self, + ty: TypeId, + node: NodeRef, + children: Vec<&Self>, + ctx: &AnyMap, + ); + fn node_dep_types(&self) -> Vec; +} diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs new file mode 100644 index 000000000..c22e897df --- /dev/null +++ b/packages/native-core/tests/parse.rs @@ -0,0 +1,62 @@ +use dioxus_native_core::real_dom_new_api::*; +use dioxus_native_core_macro::*; + +#[derive(State)] +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)] + y: B, + // depends on attributes, the C component of it's parent and a u8 context + #[parent_dep_state(u8, C)] + z: C, +} + +// struct Z { +// x: A, +// y: B, +// z: C, +// } + +use dioxus_native_core::real_dom_new_api::NodeDepState; + +#[derive(PartialEq)] +struct A; +impl NodeDepState for A { + type Ctx = (); + fn reduce(&mut self, _: NodeRef, _: &()) { + todo!() + } +} + +#[derive(PartialEq)] +struct B; +impl ChildDepState for B { + type Ctx = i32; + type DepState = Self; + fn reduce( + &mut self, + _: dioxus_native_core::real_dom_new_api::NodeRef, + _: Vec<&Self::DepState>, + _: &i32, + ) { + todo!() + } +} + +#[derive(PartialEq)] +struct C; +impl ParentDepState for C { + type Ctx = u8; + type DepState = Self; + fn reduce( + &mut self, + _: dioxus_native_core::real_dom_new_api::NodeRef, + _: &Self::DepState, + _: &u8, + ) { + todo!() + } +} From 9e9fc09e6dd77abc79ff54afdec57f9d78e042fc Mon Sep 17 00:00:00 2001 From: demonthos Date: Sat, 9 Apr 2022 16:40:26 -0500 Subject: [PATCH 2/7] WIP macro progress --- packages/native-core-macro/src/lib.rs | 80 ++++++----- packages/native-core/src/real_dom_new_api.rs | 139 +++++++++++++++---- packages/native-core/tests/parse.rs | 10 +- 3 files changed, 165 insertions(+), 64 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 883c12b2e..740c214ba 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -36,6 +36,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { }; let strct = Struct::parse(&fields); let state_strct = StateStruct::parse(&fields, &strct); + let node_dep_state_fields = quote::__private::TokenStream::from_iter( state_strct .state_members @@ -86,67 +87,77 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { .state_members .iter() .filter(|f| f.dep_kind == DepKind::NodeDepState) - .map(|f| f.type_id()); + .map(|f| &f.mem.ty); let child_types = state_strct .state_members .iter() .filter(|f| f.dep_kind == DepKind::ChildDepState) - .map(|f| f.type_id()); + .map(|f| &f.mem.ty); let parent_types = state_strct .state_members .iter() .filter(|f| f.dep_kind == DepKind::ParentDepState) - .map(|f| f.type_id()); + .map(|f| &f.mem.ty); let type_name_str = type_name.to_string(); let gen = quote! { impl State for #type_name{ - fn update_node_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, ctx: &anymap::AnyMap){ + 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; - if false {} + if false { + unreachable!(); + } #node_dep_state_fields else{ - panic!("{:?} not in {}", ty, #type_name_str); + panic!("{:?} not in {}", ty, #type_name_str) } } - fn update_parent_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, parent: &Self, ctx: &anymap::AnyMap){ + 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; - if false {} + if false { + unreachable!(); + } #parent_dep_state_fields else{ - panic!("{:?} not in {}", ty, #type_name_str); + panic!("{:?} not in {}", ty, #type_name_str) } } - fn update_child_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, children: Vec<&Self>, ctx: &anymap::AnyMap){ + 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; - if false {} + if false { + unreachable!() + } #child_dep_state_fields else{ - panic!("{:?} not in {}", ty, #type_name_str); + panic!("{:?} not in {}", ty, #type_name_str) } } - fn child_dep_types(&self) -> Vec{ - // todo: order should depend on order of dependencies - vec![ - #(#child_types,)* - ] + fn child_dep_types(&self, mask: &dioxus_native_core::real_dom_new_api::NodeMask) -> Vec{ + let mut dep_types = Vec::new(); + #(if #child_types::NODE_MASK.overlaps(mask) { + dep_types.push(std::any::TypeId::of::<#child_types>()); + })* + dep_types } - fn parent_dep_types(&self) -> Vec{ - // todo: order should depend on order of dependencies - vec![ - #(#parent_types,)* - ] + fn parent_dep_types(&self, mask: &dioxus_native_core::real_dom_new_api::NodeMask) -> Vec{ + let mut dep_types = Vec::new(); + #(if #parent_types::NODE_MASK.overlaps(mask) { + dep_types.push(std::any::TypeId::of::<#parent_types>()); + })* + dep_types } - fn node_dep_types(&self) -> Vec{ - vec![ - #(#node_types,)* - ] + fn node_dep_types(&self, mask: &dioxus_native_core::real_dom_new_api::NodeMask) -> Vec{ + let mut dep_types = Vec::new(); + #(if #node_types::NODE_MASK.overlaps(mask) { + dep_types.push(std::any::TypeId::of::<#node_types>()); + })* + dep_types } } }; @@ -176,6 +187,9 @@ impl<'a> StateStruct<'a> { .zip(fields.iter()) .filter_map(|(m, f)| StateMember::parse(f, m, &strct)) .collect(); + + // todo: sort members + Self { state_members } } } @@ -251,28 +265,30 @@ impl<'a> StateMember<'a> { } else { quote! {&()} }; + let ty = &self.mem.ty; + let node_view = quote!(NodeView::new(node, #ty::NODE_MASK)); if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) { match self.dep_kind { DepKind::NodeDepState => { - quote!(self.#ident.reduce(node, #get_ctx);) + quote!(self.#ident.reduce(#node_view, #get_ctx)) } DepKind::ChildDepState => { - quote!(self.#ident.reduce(node, children.iter().map(|s| &s.#dep_ident).collect(), #get_ctx);) + quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident).collect(), #get_ctx)) } DepKind::ParentDepState => { - quote!(self.#ident.reduce(node, &parent.#dep_ident, #get_ctx);) + quote!(self.#ident.reduce(#node_view, &parent.#dep_ident, #get_ctx)) } } } else { match self.dep_kind { DepKind::NodeDepState => { - quote!(self.#ident.reduce(node, #get_ctx);) + quote!(self.#ident.reduce(#node_view, #get_ctx)) } DepKind::ChildDepState => { - quote!(self.#ident.reduce(node, &(), #get_ctx);) + quote!(self.#ident.reduce(#node_view, &(), #get_ctx)) } DepKind::ParentDepState => { - quote!(self.#ident.reduce(node, &(), #get_ctx);) + quote!(self.#ident.reduce(#node_view, &(), #get_ctx)) } } } diff --git a/packages/native-core/src/real_dom_new_api.rs b/packages/native-core/src/real_dom_new_api.rs index 7740a97d4..269364c81 100644 --- a/packages/native-core/src/real_dom_new_api.rs +++ b/packages/native-core/src/real_dom_new_api.rs @@ -3,56 +3,141 @@ use std::any::TypeId; use anymap::AnyMap; use dioxus_core::{Attribute, VElement}; -#[repr(transparent)] -pub struct NodeRef<'a>(&'a VElement<'a>); -impl<'a> NodeRef<'a> { - pub fn new(velement: &'a VElement<'a>) -> Self { - Self(velement) +pub struct NodeView<'a> { + inner: &'a VElement<'a>, + view: NodeMask, +} +impl<'a> NodeView<'a> { + pub fn new(velement: &'a VElement<'a>, view: NodeMask) -> Self { + Self { + inner: velement, + view: view, + } } - pub fn tag(&self) -> &'a str { - self.0.tag + pub fn tag(&self) -> Option<&'a str> { + if self.view.tag { + Some(self.inner.tag) + } else { + None + } } pub fn namespace(&self) -> Option<&'a str> { - self.0.namespace + if self.view.namespace { + self.inner.namespace + } else { + None + } } - pub fn attributes(&self) -> &'a [Attribute<'a>] { - self.0.attributes + pub fn attributes(&self) -> impl Iterator> { + self.inner + .attributes + .iter() + .filter(|a| self.view.attritutes.contains(&a.name)) } } -pub trait ChildDepState: PartialEq { +#[derive(Default)] +pub struct NodeMask { + // must be sorted + attritutes: &'static [&'static str], + tag: bool, + namespace: bool, +} + +impl NodeMask { + /// attritutes must be sorted! + pub const fn new(attritutes: &'static [&'static str], tag: bool, namespace: bool) -> Self { + Self { + attritutes, + tag, + namespace, + } + } + + 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) + || self.attritutes_overlap(other) + } + + 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 + } +} + +pub trait ChildDepState { type Ctx; type DepState: ChildDepState; - fn reduce(&mut self, node: NodeRef, children: Vec<&Self::DepState>, ctx: &Self::Ctx); + const NODE_MASK: NodeMask = NodeMask::new(&[], false, false); + fn reduce(&mut self, node: NodeView, children: Vec<&Self::DepState>, ctx: &Self::Ctx) -> bool; } -pub trait ParentDepState: PartialEq { +pub trait ParentDepState { type Ctx; type DepState: ParentDepState; - fn reduce(&mut self, node: NodeRef, parent: &Self::DepState, ctx: &Self::Ctx); + const NODE_MASK: NodeMask = NodeMask::new(&[], false, false); + fn reduce(&mut self, node: NodeView, parent: &Self::DepState, ctx: &Self::Ctx) -> bool; } -pub trait NodeDepState: PartialEq { +pub trait NodeDepState { type Ctx; - fn reduce(&mut self, node: NodeRef, ctx: &Self::Ctx); + const NODE_MASK: NodeMask = NodeMask::new(&[], false, false); + fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool; } pub trait State { - fn update_node_dep_state(&mut self, ty: TypeId, node: NodeRef, ctx: &AnyMap); - fn child_dep_types(&self) -> Vec; - - fn update_parent_dep_state(&mut self, ty: TypeId, node: NodeRef, parent: &Self, ctx: &AnyMap); - fn parent_dep_types(&self) -> Vec; - - fn update_child_dep_state( - &mut self, + fn update_node_dep_state<'a>( + &'a mut self, ty: TypeId, - node: NodeRef, + node: &'a VElement<'a>, + ctx: &AnyMap, + ) -> bool; + /// 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: TypeId, + node: &'a VElement<'a>, + parent: &Self, + ctx: &AnyMap, + ) -> bool; + /// 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: TypeId, + node: &'a VElement<'a>, children: Vec<&Self>, ctx: &AnyMap, - ); - fn node_dep_types(&self) -> Vec; + ) -> bool; + /// This must be a valid resolution order. (no nodes updated before a state they rely on) + fn node_dep_types(&self, mask: &NodeMask) -> Vec; } diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs index c22e897df..8f680bf88 100644 --- a/packages/native-core/tests/parse.rs +++ b/packages/native-core/tests/parse.rs @@ -26,7 +26,7 @@ use dioxus_native_core::real_dom_new_api::NodeDepState; struct A; impl NodeDepState for A { type Ctx = (); - fn reduce(&mut self, _: NodeRef, _: &()) { + fn reduce(&mut self, _: NodeView, _: &()) -> bool { todo!() } } @@ -38,10 +38,10 @@ impl ChildDepState for B { type DepState = Self; fn reduce( &mut self, - _: dioxus_native_core::real_dom_new_api::NodeRef, + _: dioxus_native_core::real_dom_new_api::NodeView, _: Vec<&Self::DepState>, _: &i32, - ) { + ) -> bool { todo!() } } @@ -53,10 +53,10 @@ impl ParentDepState for C { type DepState = Self; fn reduce( &mut self, - _: dioxus_native_core::real_dom_new_api::NodeRef, + _: dioxus_native_core::real_dom_new_api::NodeView, _: &Self::DepState, _: &u8, - ) { + ) -> bool { todo!() } } From 13a99bce9c831db272a5190d2b889893e0db7e48 Mon Sep 17 00:00:00 2001 From: demonthos Date: Sun, 10 Apr 2022 07:00:44 -0500 Subject: [PATCH 3/7] WIP: intigrate exsisting code --- packages/native-core/src/real_dom.rs | 95 ++++++++------------ packages/native-core/src/real_dom_new_api.rs | 2 +- 2 files changed, 37 insertions(+), 60 deletions(-) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 239c90aea..f40aaf474 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,3 +1,4 @@ +use anymap::AnyMap; use fxhash::{FxHashMap, FxHashSet}; use std::{ collections::VecDeque, @@ -6,25 +7,27 @@ use std::{ use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; +use crate::real_dom_new_api::{State, NodeMask}; + /// 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. /// To get started implement [PushedDownState] and or [BubbledUpState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes. #[derive(Debug)] -pub struct RealDom { +pub struct RealDom { root: usize, - nodes: Vec>>, + nodes: Vec>>, nodes_listening: FxHashMap<&'static str, FxHashSet>, node_stack: smallvec::SmallVec<[usize; 10]>, } -impl Default for RealDom { +impl Default for RealDom { fn default() -> Self { Self::new() } } -impl RealDom { - pub fn new() -> RealDom { +impl RealDom { + pub fn new() -> RealDom { RealDom { root: 0, nodes: { @@ -191,8 +194,7 @@ impl RealDom { &mut self, vdom: &VirtualDom, nodes_updated: Vec, - us_ctx: &mut US::Ctx, - ds_ctx: &mut DS::Ctx, + ctx: AnyMap, ) -> Option> { let mut to_rerender = FxHashSet::default(); to_rerender.extend(nodes_updated.iter()); @@ -210,24 +212,13 @@ impl RealDom { let node = &mut self[id as usize]; let vnode = node.element(vdom); let node_type = &node.node_type; - let up_state = &mut node.up_state; - let children = match node_type { - NodeType::Element { children, .. } => Some(children), - _ => None, - }; + if let + NodeType::Element { children, tag, namespace } =node.node_type{ + + } + let mask = NodeMask::new(attritutes, tag, namespace) // todo: reduce cloning state - let old = up_state.clone(); - let mut new = up_state.clone(); - let parent = node.parent; - new.reduce( - children - .unwrap_or(&Vec::new()) - .clone() - .iter() - .map(|c| &self[*c].up_state), - vnode, - us_ctx, - ); + for id in node.state.child_dep_types(mask) {} if new != old { to_rerender.insert(id); if let Some(p) = parent { @@ -300,12 +291,9 @@ impl RealDom { } // remove a node and it's children from the dom. - fn remove(&mut self, id: usize) -> Option> { + fn remove(&mut self, id: usize) -> 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> { + fn inner(dom: &mut RealDom, id: usize) -> Option> { let mut node = dom.nodes[id as usize].take()?; if let NodeType::Element { children, .. } = &mut node.node_type { for c in children { @@ -327,7 +315,7 @@ impl RealDom { Some(node) } - fn insert(&mut self, node: Node) { + fn insert(&mut self, node: Node) { let current_len = self.nodes.len(); let id = node.id.0; if current_len - 1 < node.id.0 { @@ -337,15 +325,15 @@ impl RealDom { self.nodes[id] = Some(node); } - pub fn get(&self, id: usize) -> Option<&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> { + pub fn get_mut(&mut self, id: usize) -> Option<&mut Node> { self.nodes.get_mut(id)?.as_mut() } - pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node> { + 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(); listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse()); @@ -416,12 +404,8 @@ impl RealDom { } /// Call a function for each node in the dom, depth first. - pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node)) { - fn inner( - dom: &RealDom, - id: ElementId, - f: &mut impl FnMut(&Node), - ) { + pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node)) { + fn inner(dom: &RealDom, id: ElementId, f: &mut impl FnMut(&Node)) { let node = &dom[id]; f(node); if let NodeType::Element { children, .. } = &node.node_type { @@ -438,12 +422,8 @@ impl RealDom { } /// Call a function for each node in the dom, depth first. - pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node)) { - fn inner( - dom: &mut RealDom, - id: ElementId, - f: &mut impl FnMut(&mut Node), - ) { + pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node)) { + fn inner(dom: &mut RealDom, id: ElementId, f: &mut impl FnMut(&mut Node)) { let node = &mut dom[id]; f(node); if let NodeType::Element { children, .. } = &mut node.node_type { @@ -461,28 +441,28 @@ impl RealDom { } } -impl Index for RealDom { - type Output = Node; +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; +impl Index for RealDom { + type Output = Node; fn index(&self, idx: ElementId) -> &Self::Output { &self[idx.0] } } -impl IndexMut for RealDom { +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 { +impl IndexMut for RealDom { fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output { &mut self[idx.0] } @@ -490,15 +470,13 @@ impl IndexMut for RealDom { +pub struct Node { /// The id of the node this node was created from. pub id: ElementId, /// The parent id of the node. pub parent: Option, - /// State of the node that is bubbled up to its parents. The state must depend only on the node and its children. - pub up_state: US, - /// State of the node that is pushed down to the children. The state must depend only on the node itself and its parent. - pub down_state: DS, + /// State of the node. + pub state: S, /// Additional inforation specific to the node type pub node_type: NodeType, /// The number of parents before the root node. The root node has height 1. @@ -518,14 +496,13 @@ pub enum NodeType { Placeholder, } -impl Node { +impl Node { fn new(id: u64, node_type: NodeType) -> Self { Node { id: ElementId(id as usize), parent: None, node_type, - down_state: DS::default(), - up_state: US::default(), + state: S::default(), height: 0, } } diff --git a/packages/native-core/src/real_dom_new_api.rs b/packages/native-core/src/real_dom_new_api.rs index 269364c81..9cc5d0550 100644 --- a/packages/native-core/src/real_dom_new_api.rs +++ b/packages/native-core/src/real_dom_new_api.rs @@ -111,7 +111,7 @@ pub trait NodeDepState { fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool; } -pub trait State { +pub trait State: Default { fn update_node_dep_state<'a>( &'a mut self, ty: TypeId, From 92010b0babbb59b99e683929d317200ecd1e6a40 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 12 Apr 2022 07:51:57 -0500 Subject: [PATCH 4/7] 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, From 9eaf7212a3e74489dc65f3c568a0ca588330dfcc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 12 Apr 2022 18:46:16 -0500 Subject: [PATCH 5/7] tests passing, and tui updated --- packages/native-core-macro/src/lib.rs | 22 +- packages/native-core/src/lib.rs | 2 +- packages/native-core/src/real_dom.rs | 207 ++++++++++++------ .../src/{real_dom_new_api.rs => state.rs} | 189 +++++++++------- packages/native-core/tests/change_nodes.rs | 2 +- packages/native-core/tests/initial_build.rs | 2 +- packages/native-core/tests/parse.rs | 17 +- .../tests/{state.rs => update_state.rs} | 39 ++-- packages/tui/src/hooks.rs | 36 ++- packages/tui/src/layout.rs | 123 ++++++----- packages/tui/src/lib.rs | 58 ++--- packages/tui/src/render.rs | 31 ++- packages/tui/src/style_attributes.rs | 27 ++- 13 files changed, 450 insertions(+), 305 deletions(-) rename packages/native-core/src/{real_dom_new_api.rs => state.rs} (66%) rename packages/native-core/tests/{state.rs => update_state.rs} (93%) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 5ec2fceba..cc99ee746 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -105,8 +105,8 @@ 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: Option<&'a dioxus_core::VElement<'a>>, ctx: &anymap::AnyMap) -> bool{ - use dioxus_native_core::real_dom_new_api::NodeDepState as _; + fn update_node_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, ctx: &anymap::AnyMap) -> bool{ + use dioxus_native_core::state::NodeDepState as _; // println!("called update_node_dep_state with ty: {:?}", ty); if false { unreachable!(); @@ -117,8 +117,8 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { } } - 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 _; + fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, parent: Option<&Self>, ctx: &anymap::AnyMap) -> bool{ + use dioxus_native_core::state::ParentDepState as _; // println!("called update_parent_dep_state with ty: {:?}", ty); if false { unreachable!(); @@ -129,8 +129,8 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { } } - 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 _; + fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, children: &[&Self], ctx: &anymap::AnyMap) -> bool{ + use dioxus_native_core::state::ChildDepState as _; // println!("called update_child_dep_state with ty: {:?}", ty); if false { unreachable!() @@ -141,7 +141,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { } } - fn child_dep_types(&self, mask: &dioxus_native_core::real_dom_new_api::NodeMask) -> Vec{ + fn child_dep_types(&self, mask: &dioxus_native_core::state::NodeMask) -> Vec{ let mut dep_types = Vec::new(); #(if #child_types::NODE_MASK.overlaps(mask) { dep_types.push(std::any::TypeId::of::<#child_types>()); @@ -149,7 +149,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { dep_types } - fn parent_dep_types(&self, mask: &dioxus_native_core::real_dom_new_api::NodeMask) -> Vec{ + fn parent_dep_types(&self, mask: &dioxus_native_core::state::NodeMask) -> Vec{ let mut dep_types = Vec::new(); #(if #parent_types::NODE_MASK.overlaps(mask) { dep_types.push(std::any::TypeId::of::<#parent_types>()); @@ -157,7 +157,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { dep_types } - fn node_dep_types(&self, mask: &dioxus_native_core::real_dom_new_api::NodeMask) -> Vec{ + fn node_dep_types(&self, mask: &dioxus_native_core::state::NodeMask) -> Vec{ let mut dep_types = Vec::new(); #(if #node_types::NODE_MASK.overlaps(mask) { dep_types.push(std::any::TypeId::of::<#node_types>()); @@ -314,7 +314,7 @@ impl<'a> StateMember<'a> { quote!(self.#ident.reduce(#node_view, #get_ctx)) } DepKind::ChildDepState => { - quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident).collect(), #get_ctx)) + quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx)) } DepKind::ParentDepState => { quote!(self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx)) @@ -337,10 +337,8 @@ impl<'a> StateMember<'a> { fn type_id(&self) -> quote::__private::TokenStream { let ty = &self.mem.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 580380f68..7692a5ae1 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,4 +1,4 @@ pub mod layout_attributes; pub mod real_dom; -pub mod real_dom_new_api; +pub mod state; 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 55ae7e0a8..1c25806c3 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -8,7 +8,7 @@ use std::{ use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; -use crate::real_dom_new_api::{AttributeMask, NodeMask, State}; +use crate::state::{union_ordered_iter, 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. @@ -166,7 +166,10 @@ impl RealDom { text: new_text, } => { let target = &mut self[root as usize]; - nodes_updated.push((root as usize, NodeMask::NONE)); + nodes_updated.push(( + root as usize, + NodeMask::new(AttributeMask::NONE, false, false, true), + )); match &mut target.node_type { NodeType::Text { text } => { *text = new_text.to_string(); @@ -177,7 +180,7 @@ impl RealDom { SetAttribute { root, field, .. } => { nodes_updated.push(( root as usize, - NodeMask::new(AttributeMask::single(field), false, false), + NodeMask::new(AttributeMask::single(field), false, false, false), )); } RemoveAttribute { @@ -185,7 +188,7 @@ impl RealDom { } => { nodes_updated.push(( root as usize, - NodeMask::new(AttributeMask::single(field), false, false), + NodeMask::new(AttributeMask::single(field), false, false, false), )); } PopRoot {} => { @@ -210,65 +213,122 @@ impl RealDom { 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)| (id, self[id].height, mask, StatesToCheck::All)) + .map(|(id, mask)| NodeRef { + id, + height: self[id].height, + node_mask: mask, + to_check: 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))); - { - // Combine mutations that affect the same node. - let current_key = None; - for i in 0..nodes_updated.len() { - let current = nodes_updated; + 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); } } - println!("{:?}", nodes_updated); + 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 (id, _height, mask, to_check) in &nodes_updated { + for node_ref 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), + let node = &mut self[node_ref.id]; + let ids = match &node_ref.to_check { + StatesToCheck::All => node.state.node_dep_types(&node_ref.node_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); + let node = &mut self[node_ref.id]; + let vnode = node.element(vdom); + changed |= node.state.update_node_dep_state(ty, vnode, &ctx); } if changed { - to_rerender.insert(*id); + 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: VecDeque<_> = nodes_updated.clone().into(); - while let Some((id, height, mask, to_check)) = to_bubble.pop_back() { + 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 ids = match to_check { - StatesToCheck::All => node.state.child_dep_types(&mask), + StatesToCheck::All => node.state.child_dep_types(&node_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 - }; + let vnode = node.element(vdom); if node .state - .update_child_dep_state(ty, el, &children_state, &ctx) + .update_child_dep_state(ty, vnode, &children_state, &ctx) { changed.push(ty); } @@ -276,19 +336,28 @@ impl RealDom { 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) - }); + 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].0 != parent_id.0 { + 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, - ( - parent_id.0, - height - 1, - NodeMask::NONE, - StatesToCheck::Some(changed), - ), + NodeRef { + id: parent_id.0, + height: height - 1, + node_mask: NodeMask::NONE, + to_check: StatesToCheck::Some(changed), + }, ); } } @@ -296,26 +365,28 @@ impl RealDom { } // 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, mask, to_check)) = to_push.pop_front() { + let mut to_push = nodes_updated.clone(); + while let Some(node_ref) = to_push.pop_front() { + let NodeRef { + id, + height, + node_mask, + to_check, + } = node_ref; let node = &self[id]; let ids = match to_check { - StatesToCheck::All => node.state.parent_dep_types(&mask), + 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(); for ty in ids { - let el = if let &VNode::Element(e) = node.element(vdom) { - Some(e) - } else { - None - }; + let vnode = node.element(vdom); let parent = parent.as_deref(); let state = &mut node.state; if state.update_parent_dep_state( ty, - el, + vnode, parent.filter(|n| n.id.0 != 0).map(|n| &n.state), &ctx, ) { @@ -328,18 +399,28 @@ impl RealDom { 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, ..)| { - *h < height + 1 || (*h == height + 1 && *other_id < c.0) - }); - if i >= to_push.len() || to_push[i].0 != c.0 { + 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, - ( - c.0, - height + 1, - NodeMask::NONE, - StatesToCheck::Some(changed.clone()), - ), + NodeRef { + id: c.0, + height: height + 1, + node_mask: NodeMask::NONE, + to_check: StatesToCheck::Some(changed.clone()), + }, ); } } diff --git a/packages/native-core/src/real_dom_new_api.rs b/packages/native-core/src/state.rs similarity index 66% rename from packages/native-core/src/real_dom_new_api.rs rename to packages/native-core/src/state.rs index 042e625c1..1e7135580 100644 --- a/packages/native-core/src/real_dom_new_api.rs +++ b/packages/native-core/src/state.rs @@ -1,43 +1,96 @@ -use std::any::TypeId; +use std::{any::TypeId, fmt::Debug}; use anymap::AnyMap; -use dioxus_core::{Attribute, VElement}; +use dioxus_core::{Attribute, ElementId, VElement, VNode, VText}; + +pub(crate) fn union_ordered_iter( + s_iter: impl Iterator, + o_iter: impl Iterator, + new_len_guess: usize, +) -> Vec { + let mut s_peekable = s_iter.peekable(); + let mut o_peekable = o_iter.peekable(); + let mut v = Vec::with_capacity(new_len_guess); + while let Some(s_i) = s_peekable.peek() { + loop { + if let Some(o_i) = o_peekable.peek() { + if o_i > s_i { + break; + } else if o_i < s_i { + 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); + } + for w in v.windows(2) { + debug_assert!(w[1] > w[0]); + } + v +} #[derive(Debug)] pub struct NodeView<'a> { - inner: Option<&'a VElement<'a>>, - view: NodeMask, + inner: &'a VNode<'a>, + mask: NodeMask, } impl<'a> NodeView<'a> { - pub fn new(velement: Option<&'a VElement<'a>>, view: NodeMask) -> Self { + pub fn new(vnode: &'a VNode<'a>, view: NodeMask) -> Self { Self { - inner: velement, - view: view, + inner: vnode, + mask: view, } } + pub fn id(&self) -> ElementId { + self.inner.mounted_id() + } + pub fn tag(&self) -> Option<&'a str> { - if self.view.tag { - self.inner.map(|el| el.tag) - } else { - None - } + self.mask.tag.then(|| self.el().map(|el| el.tag)).flatten() } pub fn namespace(&self) -> Option<&'a str> { - if self.view.namespace { - self.inner.map(|el| el.namespace).flatten() + self.mask + .namespace + .then(|| self.el().map(|el| el.namespace).flatten()) + .flatten() + } + + pub fn attributes(&self) -> impl Iterator> { + self.el() + .map(|el| el.attributes) + .unwrap_or_default() + .iter() + .filter(|a| self.mask.attritutes.contains_attribute(&a.name)) + } + + pub fn text(&self) -> Option<&str> { + self.mask + .text + .then(|| self.txt().map(|txt| txt.text)) + .flatten() + } + + fn el(&self) -> Option<&'a VElement<'a>> { + if let VNode::Element(el) = &self.inner { + Some(el) } else { None } } - pub fn attributes(&self) -> impl Iterator> { - self.inner - .map(|el| el.attributes) - .unwrap_or_default() - .iter() - .filter(|a| self.view.attritutes.contains_attribute(&a.name)) + fn txt(&self) -> Option<&'a VText<'a>> { + if let VNode::Text(txt) = &self.inner { + Some(txt) + } else { + None + } } } @@ -54,8 +107,8 @@ impl AttributeMask { 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), + AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(), + AttributeMask::Static(l) => l.binary_search(&attr).is_ok(), } } @@ -78,46 +131,19 @@ impl AttributeMask { } 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::Dynamic(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic( + union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()), + ), + (AttributeMask::Static(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic( + union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()), + ), + (AttributeMask::Dynamic(s), AttributeMask::Static(o)) => AttributeMask::Dynamic( + union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()), + ), + (AttributeMask::Static(s), AttributeMask::Static(o)) => AttributeMask::Dynamic( + union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()), + ), _ => AttributeMask::All, }; new.verify(); @@ -179,29 +205,37 @@ pub struct NodeMask { attritutes: AttributeMask, tag: bool, namespace: bool, + text: bool, } impl NodeMask { - pub const NONE: Self = Self::new(AttributeMask::Static(&[]), false, false); - pub const ALL: Self = Self::new(AttributeMask::All, true, true); + pub const NONE: Self = Self::new(AttributeMask::Static(&[]), false, false, false); + pub const ALL: Self = Self::new(AttributeMask::All, true, true, true); /// attritutes must be sorted! - pub const fn new(attritutes: AttributeMask, tag: bool, namespace: bool) -> Self { + pub const fn new(attritutes: AttributeMask, tag: bool, namespace: bool, text: bool) -> Self { Self { attritutes, tag, namespace, + text, } } pub fn overlaps(&self, other: &Self) -> bool { (self.tag && other.tag) || (self.namespace && other.namespace) - || self.attritutes_overlap(other) + || self.attritutes.overlaps(&other.attritutes) + || (self.text && other.text) } - fn attritutes_overlap(&self, other: &Self) -> bool { - self.attritutes.overlaps(&other.attritutes) + pub fn union(&self, other: &Self) -> Self { + Self { + attritutes: self.attritutes.union(&other.attritutes), + tag: self.tag | other.tag, + namespace: self.namespace | other.namespace, + text: self.text | other.text, + } } } @@ -213,8 +247,15 @@ pub trait ChildDepState { /// This is sometimes nessisary for lifetime purposes. type Ctx; type DepState: ChildDepState; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false); - fn reduce(&mut self, node: NodeView, children: Vec<&Self::DepState>, ctx: &Self::Ctx) -> bool; + const NODE_MASK: NodeMask = NodeMask::NONE; + fn reduce<'a>( + &mut self, + node: NodeView, + children: impl Iterator, + ctx: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a; } /// This state that is passed down to children. For example text properties (`` `` ``) would be passed to children. @@ -225,7 +266,7 @@ pub trait ParentDepState { /// This is sometimes nessisary for lifetime purposes. type Ctx; type DepState: ParentDepState; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false); + const NODE_MASK: NodeMask = NodeMask::NONE; fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool; } @@ -234,7 +275,7 @@ pub trait ParentDepState { /// Called at most once per update. pub trait NodeDepState { type Ctx; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false); + const NODE_MASK: NodeMask = NodeMask::NONE; fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool; } @@ -242,7 +283,7 @@ pub trait State: Default + Clone { fn update_node_dep_state<'a>( &'a mut self, ty: TypeId, - node: Option<&'a VElement<'a>>, + node: &'a VNode<'a>, ctx: &AnyMap, ) -> bool; /// This must be a valid resolution order. (no nodes updated before a state they rely on) @@ -251,7 +292,7 @@ pub trait State: Default + Clone { fn update_parent_dep_state<'a>( &'a mut self, ty: TypeId, - node: Option<&'a VElement<'a>>, + node: &'a VNode<'a>, parent: Option<&Self>, ctx: &AnyMap, ) -> bool; @@ -261,7 +302,7 @@ pub trait State: Default + Clone { fn update_child_dep_state<'a>( &'a mut self, ty: TypeId, - node: Option<&'a VElement<'a>>, + node: &'a VNode<'a>, children: &[&Self], ctx: &AnyMap, ) -> bool; diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core/tests/change_nodes.rs index bf3de4368..8f4e6c8f0 100644 --- a/packages/native-core/tests/change_nodes.rs +++ b/packages/native-core/tests/change_nodes.rs @@ -3,7 +3,7 @@ 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::state::State; use dioxus_native_core_macro::State; use std::cell::Cell; diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core/tests/initial_build.rs index 321820b7b..750987a29 100644 --- a/packages/native-core/tests/initial_build.rs +++ b/packages/native-core/tests/initial_build.rs @@ -5,7 +5,7 @@ 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::state::State; use dioxus_native_core_macro::State; #[derive(State, Default, Clone)] diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs index a3cf69ecf..a0bbd4f27 100644 --- a/packages/native-core/tests/parse.rs +++ b/packages/native-core/tests/parse.rs @@ -1,4 +1,4 @@ -use dioxus_native_core::real_dom_new_api::*; +use dioxus_native_core::state::*; use dioxus_native_core_macro::*; #[derive(State, Default, Clone)] @@ -20,7 +20,7 @@ struct Z { // z: C, // } -use dioxus_native_core::real_dom_new_api::NodeDepState; +use dioxus_native_core::state::NodeDepState; #[derive(Default, Clone)] struct A; @@ -36,12 +36,15 @@ struct B; impl ChildDepState for B { type Ctx = i32; type DepState = Self; - fn reduce( + fn reduce<'a>( &mut self, - _: dioxus_native_core::real_dom_new_api::NodeView, - _: Vec<&Self::DepState>, + _: dioxus_native_core::state::NodeView, + _: impl Iterator, _: &i32, - ) -> bool { + ) -> bool + where + Self::DepState: 'a, + { todo!() } } @@ -53,7 +56,7 @@ impl ParentDepState for C { type DepState = Self; fn reduce( &mut self, - _: dioxus_native_core::real_dom_new_api::NodeView, + _: dioxus_native_core::state::NodeView, _: Option<&Self::DepState>, _: &u8, ) -> bool { diff --git a/packages/native-core/tests/state.rs b/packages/native-core/tests/update_state.rs similarity index 93% rename from packages/native-core/tests/state.rs rename to packages/native-core/tests/update_state.rs index 2a17ac520..5c614922f 100644 --- a/packages/native-core/tests/state.rs +++ b/packages/native-core/tests/update_state.rs @@ -4,7 +4,7 @@ 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::{ +use dioxus_native_core::state::{ AttributeMask, ChildDepState, NodeDepState, NodeMask, NodeView, ParentDepState, State, }; use dioxus_native_core_macro::State; @@ -25,12 +25,15 @@ impl ChildDepState for ChildDepCallCounter { type Ctx = (); type DepState = Self; const NODE_MASK: NodeMask = NodeMask::ALL; - fn reduce( + fn reduce<'a>( &mut self, - _node: NodeView, - _children: Vec<&Self::DepState>, - _ctx: &Self::Ctx, - ) -> bool { + _: NodeView, + _: impl Iterator, + _: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { self.0 += 1; true } @@ -70,7 +73,15 @@ impl ChildDepState for BubbledUpStateTester { type Ctx = u32; 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 { + fn reduce<'a>( + &mut self, + node: NodeView, + children: impl Iterator, + ctx: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { assert_eq!(*ctx, 42); *self = BubbledUpStateTester( node.tag().map(|s| s.to_string()), @@ -245,6 +256,10 @@ fn state_reduce_initally_called_minimally() { let nodes_updated = dom.apply_mutations(vec![mutations]); let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); + dom.traverse_depth_first(|n| { + println!("{:#?}", n.state); + }); + dom.traverse_depth_first(|n| { assert_eq!(n.state.child_counter.0, 1); assert_eq!(n.state.parent_counter.0, 1); @@ -314,10 +329,10 @@ fn state_reduce_down_called_minimally_on_update() { let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { - println!("{:?}", n.state); - // assert_eq!(n.state.parent_counter.0, 2); + // println!("{:?}", n.state); + assert_eq!(n.state.parent_counter.0, 2); }); - panic!() + // panic!() } #[test] @@ -382,8 +397,6 @@ fn state_reduce_up_called_minimally_on_update() { let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { - println!("{:?}", n.state); - // assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 }); + assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 }); }); - panic!() } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 512fab8ba..434e9e7c5 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -5,7 +5,6 @@ use dioxus_core::*; use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::{on::*, KeyCode}; -use dioxus_native_core::real_dom::{Node, RealDom}; use std::{ any::Any, cell::RefCell, @@ -17,6 +16,7 @@ use stretch2::{prelude::Layout, Stretch}; use crate::layout::StretchLayout; use crate::style_attributes::StyleModifier; +use crate::{Dom, Node}; // a wrapper around the input state for easier access // todo: fix loop @@ -166,7 +166,7 @@ impl InnerInputState { evts: &mut Vec, resolved_events: &mut Vec, layout: &Stretch, - dom: &mut RealDom, + dom: &mut Dom, ) { let previous_mouse = self .mouse @@ -191,7 +191,7 @@ impl InnerInputState { previous_mouse: Option<(MouseData, Vec)>, resolved_events: &mut Vec, layout: &Stretch, - dom: &mut RealDom, + dom: &mut Dom, ) { struct Data<'b> { new_pos: (i32, i32), @@ -215,8 +215,8 @@ impl InnerInputState { data: Arc, will_bubble: &mut FxHashSet, resolved_events: &mut Vec, - node: &Node, - dom: &RealDom, + node: &Node, + dom: &Dom, ) { // only trigger event if the event was not triggered already by a child if will_bubble.insert(node.id) { @@ -261,7 +261,7 @@ impl InnerInputState { // mousemove let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mousemove") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let previously_contained = data .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -285,7 +285,7 @@ impl InnerInputState { // mouseenter let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseenter") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let previously_contained = data .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -309,7 +309,7 @@ impl InnerInputState { // mouseover let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseover") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let previously_contained = data .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -333,7 +333,7 @@ impl InnerInputState { // mousedown let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mousedown") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); if currently_contains && data.clicked { @@ -353,7 +353,7 @@ impl InnerInputState { // mouseup let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseup") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); if currently_contains && data.released { @@ -373,7 +373,7 @@ impl InnerInputState { // click let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("click") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); if currently_contains && data.released && data.mouse_data.button == 0 { @@ -393,7 +393,7 @@ impl InnerInputState { // contextmenu let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("contextmenu") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); if currently_contains && data.released && data.mouse_data.button == 2 { @@ -413,7 +413,7 @@ impl InnerInputState { // wheel let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("wheel") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); if let Some(w) = data.wheel_data { @@ -435,7 +435,7 @@ impl InnerInputState { // mouseleave let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseleave") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let previously_contained = data .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -459,7 +459,7 @@ impl InnerInputState { // mouseout let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseout") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let previously_contained = data .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -522,11 +522,7 @@ impl RinkInputHandler { ) } - pub fn get_events( - &self, - layout: &Stretch, - dom: &mut RealDom, - ) -> Vec { + pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec { let mut resolved_events = Vec::new(); (*self.state).borrow_mut().update( diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 4224fbdd5..5924a0fd1 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -1,83 +1,90 @@ +use std::cell::RefCell; +use std::rc::Rc; + use dioxus_core::*; use dioxus_native_core::layout_attributes::apply_layout_attributes; -use dioxus_native_core::real_dom::BubbledUpState; +use dioxus_native_core::state::{AttributeMask, ChildDepState, NodeMask, NodeView}; use stretch2::prelude::*; -/// the size #[derive(Clone, PartialEq, Default, Debug)] pub struct StretchLayout { pub style: Style, pub node: Option, } -impl BubbledUpState for StretchLayout { - type Ctx = Stretch; +impl ChildDepState for StretchLayout { + type Ctx = Rc>; + type DepState = Self; + // todo: update mask + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, false, false, true); /// Setup the layout - fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, stretch: &mut Self::Ctx) + fn reduce<'a>( + &mut self, + node: NodeView, + children: impl Iterator, + ctx: &Self::Ctx, + ) -> bool where - I: Iterator, - Self: 'a, + Self::DepState: 'a, { - match vnode { - VNode::Text(t) => { - let char_len = t.text.chars().count(); + let mut stretch = ctx.borrow_mut(); + if let Some(text) = node.text() { + let char_len = text.chars().count(); - let style = Style { - size: Size { - // characters are 1 point tall - height: Dimension::Points(1.0), + let style = Style { + size: Size { + // characters are 1 point tall + height: Dimension::Points(1.0), - // text is as long as it is declared - width: Dimension::Points(char_len as f32), - }, - ..Default::default() - }; + // text is as long as it is declared + width: Dimension::Points(char_len as f32), + }, + ..Default::default() + }; - if let Some(n) = self.node { - if self.style != style { - stretch.set_style(n, style).unwrap(); - } - } else { - self.node = Some(stretch.new_node(style, &[]).unwrap()); + if let Some(n) = self.node { + if self.style != style { + stretch.set_style(n, style).unwrap(); } - - self.style = style; + } else { + self.node = Some(stretch.new_node(style, &[]).unwrap()); } - VNode::Element(el) => { - // gather up all the styles from the attribute list - let mut style = Style::default(); - for &Attribute { name, value, .. } in el.attributes { - apply_layout_attributes(name, value, &mut style); - } + self.style = style; + } else { + // gather up all the styles from the attribute list + let mut style = Style::default(); - // the root node fills the entire area - if el.id.get() == Some(ElementId(0)) { - apply_layout_attributes("width", "100%", &mut style); - apply_layout_attributes("height", "100%", &mut style); - } - - // Set all direct nodes as our children - let mut child_layout = vec![]; - for l in children { - child_layout.push(l.node.unwrap()); - } - - if let Some(n) = self.node { - if stretch.children(n).unwrap() != child_layout { - stretch.set_children(n, &child_layout).unwrap(); - } - if self.style != style { - stretch.set_style(n, style).unwrap(); - } - } else { - self.node = Some(stretch.new_node(style, &child_layout).unwrap()); - } - - self.style = style; + for &Attribute { name, value, .. } in node.attributes() { + apply_layout_attributes(name, value, &mut style); } - _ => (), + + // the root node fills the entire area + if node.id() == ElementId(0) { + apply_layout_attributes("width", "100%", &mut style); + apply_layout_attributes("height", "100%", &mut style); + } + + // Set all direct nodes as our children + let mut child_layout = vec![]; + for l in children { + child_layout.push(l.node.unwrap()); + } + + if let Some(n) = self.node { + if stretch.children(n).unwrap() != child_layout { + stretch.set_children(n, &child_layout).unwrap(); + } + if self.style != style { + stretch.set_style(n, style).unwrap(); + } + } else { + self.node = Some(stretch.new_node(style, &child_layout).unwrap()); + } + + self.style = style; } + true } } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 4aa9b1313..1286eba06 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use anymap::AnyMap; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers}, execute, @@ -6,14 +7,14 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::{ - dioxus_native_core_macro::State, real_dom::RealDom, real_dom_new_api::State, -}; +use dioxus_native_core::{dioxus_native_core_macro::State, real_dom::RealDom, state::*}; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, pin_mut, StreamExt, }; use layout::StretchLayout; +use std::cell::RefCell; +use std::rc::Rc; use std::{io, time::Duration}; use stretch2::{prelude::Size, Stretch}; use style_attributes::StyleModifier; @@ -31,8 +32,17 @@ pub use config::*; pub use hooks::*; pub use render::*; +type Dom = RealDom; +type Node = dioxus_native_core::real_dom::Node; + #[derive(Debug, Clone, State, Default)] -struct NodeState {} +struct NodeState { + #[child_dep_state(StretchLayout, RefCell)] + layout: StretchLayout, + // depends on attributes, the C component of it's parent and a u8 context + #[parent_dep_state(StyleModifier)] + style: StyleModifier, +} #[derive(Clone)] pub struct TuiContext { @@ -75,13 +85,13 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { cx.provide_root_context(state); cx.provide_root_context(TuiContext { tx: event_tx_clone }); - let mut rdom: RealDom = RealDom::new(); + let mut rdom: Dom = RealDom::new(); let mutations = dom.rebuild(); let to_update = rdom.apply_mutations(vec![mutations]); - let mut stretch = Stretch::new(); - let _to_rerender = rdom - .update_state(&dom, to_update, &mut stretch, &mut ()) - .unwrap(); + let stretch = Rc::new(RefCell::new(Stretch::new())); + let mut any_map = AnyMap::new(); + any_map.insert(stretch.clone()); + let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap(); render_vdom( &mut dom, @@ -100,8 +110,8 @@ fn render_vdom( mut event_reciever: UnboundedReceiver, handler: RinkInputHandler, cfg: Config, - mut rdom: RealDom, - mut stretch: Stretch, + mut rdom: Dom, + mut stretch: Rc>, mut register_event: impl FnMut(crossterm::event::Event), ) -> Result<()> { tokio::runtime::Builder::new_current_thread() @@ -119,7 +129,7 @@ fn render_vdom( terminal.clear().unwrap(); } - let mut to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); + let to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); let mut resized = true; loop { @@ -136,15 +146,11 @@ fn render_vdom( if !to_rerender.is_empty() || resized { resized = false; - fn resize( - dims: Rect, - stretch: &mut Stretch, - rdom: &RealDom, - ) { + fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) { let width = dims.width; let height = dims.height; let root_id = rdom.root_id(); - let root_node = rdom[root_id].up_state.node.unwrap(); + let root_node = rdom[root_id].state.layout.node.unwrap(); stretch .compute_layout( @@ -159,9 +165,9 @@ fn render_vdom( if let Some(terminal) = &mut terminal { terminal.draw(|frame| { // size is guaranteed to not change when rendering - resize(frame.size(), &mut stretch, &rdom); + resize(frame.size(), &mut stretch.borrow_mut(), &rdom); let root = &rdom[rdom.root_id()]; - render::render_vnode(frame, &stretch, &rdom, &root, cfg); + render::render_vnode(frame, &stretch.borrow(), &rdom, &root, cfg); })?; } else { resize( @@ -171,7 +177,7 @@ fn render_vdom( width: 300, height: 300, }, - &mut stretch, + &mut stretch.borrow_mut(), &rdom, ); } @@ -212,7 +218,7 @@ fn render_vdom( { // resolve events before rendering - let evts = handler.get_events(&stretch, &mut rdom); + let evts = handler.get_events(&stretch.borrow(), &mut rdom); for e in evts { vdom.handle_message(SchedulerMsg::Event(e)); } @@ -220,11 +226,9 @@ fn render_vdom( // updates the dom's nodes let to_update = rdom.apply_mutations(mutations); // update the style and layout - to_rerender.extend( - rdom.update_state(vdom, to_update, &mut stretch, &mut ()) - .unwrap() - .iter(), - ) + let mut any_map = AnyMap::new(); + any_map.insert(stretch.clone()); + let _to_rerender = rdom.update_state(&vdom, to_update, any_map).unwrap(); } } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 94cd90a62..8cee7c55c 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,8 +1,5 @@ use crate::layout::StretchLayout; -use dioxus_native_core::{ - layout_attributes::UnitSystem, - real_dom::{Node, RealDom}, -}; +use dioxus_native_core::layout_attributes::UnitSystem; use std::io::Stdout; use stretch2::{ geometry::Point, @@ -15,16 +12,16 @@ use crate::{ style::{RinkColor, RinkStyle}, style_attributes::{BorderEdge, BorderStyle}, widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext}, - Config, StyleModifier, + Config, Dom, Node, StyleModifier, }; const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; -pub fn render_vnode( +pub(crate) fn render_vnode( frame: &mut tui::Frame>, layout: &Stretch, - rdom: &RealDom, - node: &Node, + rdom: &Dom, + node: &Node, cfg: Config, ) { use dioxus_native_core::real_dom::NodeType; @@ -33,7 +30,7 @@ pub fn render_vnode( return; } - let Layout { location, size, .. } = layout.layout(node.up_state.node.unwrap()).unwrap(); + let Layout { location, size, .. } = layout.layout(node.state.layout.node.unwrap()).unwrap(); let Point { x, y } = location; let Size { width, height } = size; @@ -59,7 +56,7 @@ pub fn render_vnode( let label = Label { text, - style: node.down_state.style, + style: node.state.style.style, }; let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); @@ -84,7 +81,7 @@ pub fn render_vnode( } } -impl RinkWidget for &Node { +impl RinkWidget for &Node { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { use tui::symbols::line::*; @@ -268,14 +265,14 @@ impl RinkWidget for &Node { for x in area.left()..area.right() { for y in area.top()..area.bottom() { let mut new_cell = RinkCell::default(); - if let Some(c) = self.down_state.style.bg { + if let Some(c) = self.state.style.style.bg { new_cell.bg = c; } buf.set(x, y, new_cell); } } - let borders = &self.down_state.modifier.borders; + let borders = &self.state.style.modifier.borders; let last_edge = &borders.left; let current_edge = &borders.top; @@ -292,7 +289,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.down_state.style.fg); + let color = current_edge.color.or(self.state.style.style.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -327,7 +324,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.down_state.style.fg); + let color = current_edge.color.or(self.state.style.style.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -362,7 +359,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.down_state.style.fg); + let color = current_edge.color.or(self.state.style.style.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -397,7 +394,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.down_state.style.fg); + let color = current_edge.color.or(self.state.style.style.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 710610104..5438eb53d 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -32,7 +32,7 @@ use dioxus_core::{Attribute, VNode}; use dioxus_native_core::{ layout_attributes::{parse_value, UnitSystem}, - real_dom::PushedDownState, + state::{AttributeMask, NodeMask, NodeView, ParentDepState}, }; use crate::style::{RinkColor, RinkStyle}; @@ -43,18 +43,22 @@ pub struct StyleModifier { pub modifier: TuiModifier, } -impl PushedDownState for StyleModifier { +impl ParentDepState for StyleModifier { type Ctx = (); + type DepState = Self; + // todo: seperate each attribute into it's own class + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, true, false); - fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, _ctx: &mut Self::Ctx) { + fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool { *self = StyleModifier::default(); if parent.is_some() { self.style.fg = None; } - if let VNode::Element(el) = vnode { - // handle text modifier elements - if el.namespace.is_none() { - match el.tag { + + // handle text modifier elements + if node.namespace().is_none() { + if let Some(tag) = node.tag() { + match tag { "b" => apply_style_attributes("font-weight", "bold", self), "strong" => apply_style_attributes("font-weight", "bold", self), "u" => apply_style_attributes("text-decoration", "underline", self), @@ -68,11 +72,11 @@ impl PushedDownState for StyleModifier { _ => (), } } + } - // gather up all the styles from the attribute list - for &Attribute { name, value, .. } in el.attributes { - apply_style_attributes(name, value, self); - } + // gather up all the styles from the attribute list + for &Attribute { name, value, .. } in node.attributes() { + apply_style_attributes(name, value, self); } // keep the text styling from the parent element @@ -81,6 +85,7 @@ impl PushedDownState for StyleModifier { new_style.bg = self.style.bg; self.style = new_style; } + true } } From 2b383bc4f4dab4871c61323ac84be689795df1c6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 12 Apr 2022 18:54:26 -0500 Subject: [PATCH 6/7] fixed test --- packages/native-core/tests/update_state.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/native-core/tests/update_state.rs b/packages/native-core/tests/update_state.rs index 5c614922f..d4825fdd5 100644 --- a/packages/native-core/tests/update_state.rs +++ b/packages/native-core/tests/update_state.rs @@ -72,7 +72,7 @@ struct BubbledUpStateTester(Option, Vec>); impl ChildDepState for BubbledUpStateTester { type Ctx = u32; type DepState = Self; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false); + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false, false); fn reduce<'a>( &mut self, node: NodeView, @@ -96,7 +96,7 @@ struct PushedDownStateTester(Option, Option>) impl ParentDepState for PushedDownStateTester { type Ctx = u32; type DepState = Self; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false); + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false, false); fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = PushedDownStateTester( @@ -121,7 +121,7 @@ struct StateTester { struct NodeStateTester(Option, Vec<(String, String)>); impl NodeDepState for NodeStateTester { type Ctx = u32; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false); + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false, false); fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = NodeStateTester( From 71707fec096056767b94b02e95c5dba706d74d7a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 12 Apr 2022 18:58:25 -0500 Subject: [PATCH 7/7] pass cargo check --- packages/tui/src/hooks.rs | 2 -- packages/tui/src/lib.rs | 3 +-- packages/tui/src/render.rs | 3 +-- packages/tui/src/style_attributes.rs | 4 ++-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 434e9e7c5..c09eed50b 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -14,8 +14,6 @@ use std::{ }; use stretch2::{prelude::Layout, Stretch}; -use crate::layout::StretchLayout; -use crate::style_attributes::StyleModifier; use crate::{Dom, Node}; // a wrapper around the input state for easier access diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 1286eba06..872356efb 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -30,7 +30,6 @@ mod widget; pub use config::*; pub use hooks::*; -pub use render::*; type Dom = RealDom; type Node = dioxus_native_core::real_dom::Node; @@ -111,7 +110,7 @@ fn render_vdom( handler: RinkInputHandler, cfg: Config, mut rdom: Dom, - mut stretch: Rc>, + stretch: Rc>, mut register_event: impl FnMut(crossterm::event::Event), ) -> Result<()> { tokio::runtime::Builder::new_current_thread() diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 8cee7c55c..e3f03f0d8 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,4 +1,3 @@ -use crate::layout::StretchLayout; use dioxus_native_core::layout_attributes::UnitSystem; use std::io::Stdout; use stretch2::{ @@ -12,7 +11,7 @@ use crate::{ style::{RinkColor, RinkStyle}, style_attributes::{BorderEdge, BorderStyle}, widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext}, - Config, Dom, Node, StyleModifier, + Config, Dom, Node, }; const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 5438eb53d..b49707668 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -29,7 +29,7 @@ - [ ] pub aspect_ratio: Number, */ -use dioxus_core::{Attribute, VNode}; +use dioxus_core::Attribute; use dioxus_native_core::{ layout_attributes::{parse_value, UnitSystem}, state::{AttributeMask, NodeMask, NodeView, ParentDepState}, @@ -49,7 +49,7 @@ impl ParentDepState for StyleModifier { // todo: seperate each attribute into it's own class const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, true, false); - fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool { + fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool { *self = StyleModifier::default(); if parent.is_some() { self.style.fg = None;