diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml index d71b03246..e80d67d81 100644 --- a/packages/native-core-macro/Cargo.toml +++ b/packages/native-core-macro/Cargo.toml @@ -10,3 +10,13 @@ proc-macro = true [dependencies] syn = { version = "1.0.11", features = ["extra-traits"] } quote = "1.0" +dioxus-native-core = { path = "../native-core" } + +[dev-dependencies] +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" } + +smallvec = "1.6" +fxhash = "0.2" +anymap = "0.12.1" \ No newline at end of file diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index a4c7fed7c..658653c19 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -1,38 +1,17 @@ extern crate proc_macro; -use std::collections::BTreeMap; +mod sorted_slice; +use dioxus_native_core::state::MemberId; use proc_macro::TokenStream; -use quote::{quote, ToTokens}; +use quote::{quote, ToTokens, __private::Span}; +use sorted_slice::StrSlice; use syn::{ - self, bracketed, + self, parse::{Parse, ParseStream, Result}, - parse_macro_input, - punctuated::Punctuated, - token::Paren, - Field, Ident, LitStr, Token, Type, TypeTuple, + parse_macro_input, parse_quote, Error, Field, Ident, Token, Type, }; -struct StrSlice { - map: BTreeMap, -} - -impl Parse for StrSlice { - fn parse(input: ParseStream) -> Result { - let content; - bracketed!(content in input); - let mut map = BTreeMap::new(); - while let Ok(s) = content.parse::() { - map.insert(s.value(), s); - #[allow(unused_must_use)] - { - content.parse::(); - } - } - Ok(StrSlice { map }) - } -} - #[proc_macro] pub fn sorted_str_slice(input: TokenStream) -> TokenStream { let slice: StrSlice = parse_macro_input!(input as StrSlice); @@ -40,14 +19,13 @@ pub fn sorted_str_slice(input: TokenStream) -> TokenStream { quote!([#(#strings, )*]).into() } -#[derive(PartialEq)] +#[derive(PartialEq, Debug, Clone)] 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(); @@ -60,153 +38,149 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { 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"), + syn::Fields::Unit => todo!("unit structs"), } .iter() .collect(), _ => unimplemented!(), }; - let strct = Struct::parse(&fields); - let state_strct = StateStruct::parse(&fields, &strct); + let strct = Struct::new(type_name.clone(), &fields); + match StateStruct::parse(&fields, &strct) { + Ok(state_strct) => { + let node_dep_state_fields = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::NodeDepState) + .map(|f| f.reduce_self()); + let child_dep_state_fields = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ChildDepState) + .map(|f| f.reduce_self()); + let parent_dep_state_fields = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ParentDepState) + .map(|f| f.reduce_self()); - 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 node_iter = state_strct + .state_members + .iter() + .filter(|m| m.dep_kind == DepKind::NodeDepState); + let node_ids = node_iter.clone().map(|m| m.member_id.0); + let node_ids_clone = node_ids.clone(); + let node_types = node_iter.map(|f| &f.mem.ty); + + let child_iter = state_strct + .state_members + .iter() + .filter(|m| m.dep_kind == DepKind::ChildDepState); + let child_ids = child_iter.clone().map(|m| m.member_id.0); + let child_ids_clone = child_ids.clone(); + let child_types = child_iter.map(|f| &f.mem.ty); + + let parent_iter = state_strct + .state_members + .iter() + .filter(|m| m.dep_kind == DepKind::ParentDepState); + let parent_ids = parent_iter.clone().map(|m| m.member_id.0); + let parent_ids_clone = parent_ids.clone(); + let parent_types = parent_iter.map(|f| &f.mem.ty); + + let type_name_str = type_name.to_string(); + + let gen = quote! { + impl State for #type_name{ + fn update_node_dep_state<'a>( + &'a mut self, + ty: dioxus_native_core::state::MemberId, + node: &'a dioxus_core::VNode<'a>, + vdom: &'a dioxus_core::VirtualDom, + ctx: &anymap::AnyMap, + ) -> Option{ + use dioxus_native_core::state::NodeDepState as _; + match ty.0{ + #( + #node_ids => #node_dep_state_fields, + )* + _ => panic!("{:?} not in {}", ty, #type_name_str), + } + } + + fn update_parent_dep_state<'a>( + &'a mut self, + ty: dioxus_native_core::state::MemberId, + node: &'a dioxus_core::VNode<'a>, + vdom: &'a dioxus_core::VirtualDom, + parent: Option<&Self>, + ctx: &anymap::AnyMap, + ) -> Option{ + use dioxus_native_core::state::ParentDepState as _; + match ty.0{ + #( + #parent_ids => #parent_dep_state_fields, + )* + _ => panic!("{:?} not in {}", ty, #type_name_str), + } + } + + fn update_child_dep_state<'a>( + &'a mut self, + ty: dioxus_native_core::state::MemberId, + node: &'a dioxus_core::VNode<'a>, + vdom: &'a dioxus_core::VirtualDom, + children: &[&Self], + ctx: &anymap::AnyMap, + ) -> Option{ + use dioxus_native_core::state::ChildDepState as _; + match ty.0{ + #( + #child_ids => {#child_dep_state_fields}, + )* + _ => panic!("{:?} not in {}", ty, #type_name_str), + } + } + + fn child_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec{ + let mut dep_types = Vec::new(); + #(if #child_types::NODE_MASK.overlaps(mask) { + dep_types.push(dioxus_native_core::state::MemberId(#child_ids_clone)); + })* + dep_types + } + + fn parent_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec{ + let mut dep_types = Vec::new(); + #(if #parent_types::NODE_MASK.overlaps(mask) { + dep_types.push(dioxus_native_core::state::MemberId(#parent_ids_clone)); + })* + dep_types + } + + fn node_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec{ + let mut dep_types = Vec::new(); + #(if #node_types::NODE_MASK.overlaps(mask) { + dep_types.push(dioxus_native_core::state::MemberId(#node_ids_clone)); + })* + dep_types } } - }), - ); - 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.mem.ty); - let child_types = state_strct - .state_members - .iter() - .filter(|f| f.dep_kind == DepKind::ChildDepState) - .map(|f| &f.mem.ty); - let parent_types = state_strct - .state_members - .iter() - .filter(|f| f.dep_kind == DepKind::ParentDepState) - .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<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, vdom: &'a dioxus_core::VirtualDom, ctx: &anymap::AnyMap) -> bool{ - use dioxus_native_core::state::NodeDepState as _; - // println!("called update_node_dep_state with ty: {:?}", ty); - if false { - unreachable!(); - } - #node_dep_state_fields - else{ - panic!("{:?} not in {}", ty, #type_name_str) - } - } - - fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, vdom: &'a dioxus_core::VirtualDom, 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!(); - } - #parent_dep_state_fields - else{ - panic!("{:?} not in {}", ty, #type_name_str) - } - } - - fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, vdom: &'a dioxus_core::VirtualDom, 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!() - } - #child_dep_state_fields - else{ - panic!("{:?} not in {}", ty, #type_name_str) - } - } - - 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>()); - })* - dep_types - } - - 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>()); - })* - dep_types - } - - 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>()); - })* - dep_types - } + }; + gen.into() } - }; - gen.into() + Err(e) => e.into_compile_error().into(), + } } struct Struct { + name: Ident, members: Vec, } impl Struct { - fn parse(fields: &[&Field]) -> Self { + fn new(name: Ident, fields: &[&Field]) -> Self { let members = fields.iter().filter_map(|f| Member::parse(f)).collect(); - Self { members } + Self { name, members } } } @@ -215,57 +189,198 @@ struct StateStruct<'a> { } impl<'a> StateStruct<'a> { - fn parse(fields: &[&'a Field], strct: &'a Struct) -> Self { - let state_members = strct + fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result { + let mut parse_err = Ok(()); + let state_members: Vec<_> = strct .members .iter() .zip(fields.iter()) - .filter_map(|(m, f)| StateMember::parse(f, m, &strct)) + .filter_map(|(m, f)| match StateMember::parse(f, m, &strct) { + Ok(m) => m, + Err(err) => { + parse_err = Err(err); + None + } + }) .collect(); + parse_err?; - // todo: sort members + #[derive(Debug, Clone)] + struct DepNode<'a> { + state_mem: StateMember<'a>, + depandants: Vec>>, + } + impl<'a> DepNode<'a> { + fn new(state_mem: StateMember<'a>) -> Self { + Self { + state_mem, + depandants: Vec::new(), + } + } - Self { state_members } - } -} + /// flattens the node in pre order + fn flatten(self) -> Vec> { + let DepNode { + state_mem, + depandants, + } = self; + let mut flat = vec![state_mem]; + for d in depandants { + flat.append(&mut d.flatten()); + } + flat + } -struct DepTypes { - ctx_ty: Option, - dep_ty: Option, -} + fn set_ids(&mut self, current_id: &mut usize) { + self.state_mem.member_id = dioxus_native_core::state::MemberId(*current_id); + // if the node depends on itself, we need to add the dependency seperately + if let Some(dep) = self.state_mem.dep_mem { + if dep == self.state_mem.mem { + self.state_mem + .dependants + .push((MemberId(*current_id), self.state_mem.dep_kind.clone())); + } + } + *current_id += 1; + for d in &mut self.depandants { + self.state_mem + .dependants + .push((MemberId(*current_id), d.state_mem.dep_kind.clone())); + d.set_ids(current_id); + } + } -impl Parse for DepTypes { - 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, - }) - } -} + fn contains_member(&self, member: &Member) -> bool { + if self.state_mem.mem == member { + true + } else { + self.depandants.iter().any(|d| d.contains_member(member)) + } + } -struct NodeDepTypes { - ctx_ty: Option, -} + // check if there are any mixed child/parent dependancies + fn check(&self) -> Option { + self.kind().err() + } -impl Parse for NodeDepTypes { - fn parse(input: ParseStream) -> Result { - let ctx_ty = input.parse().ok(); - Ok(Self { ctx_ty }) - } -} + fn kind(&self) -> Result<&DepKind> { + fn reduce_kind<'a>(dk1: &'a DepKind, dk2: &'a DepKind) -> Result<&'a DepKind> { + match (dk1, dk2) { + (DepKind::ChildDepState, DepKind::ParentDepState) + | (DepKind::ParentDepState, DepKind::ChildDepState) => Err(Error::new( + Span::call_site(), + "There is a ChildDepState that depends on a ParentDepState", + )), + // node dep state takes the lowest priority + (DepKind::NodeDepState, important) | (important, DepKind::NodeDepState) => { + Ok(important) + } + // they are the same + (fst, _) => Ok(fst), + } + } + reduce_kind( + self.depandants + .iter() + .try_fold(&DepKind::NodeDepState, |dk1, dk2| { + reduce_kind(dk1, dk2.kind()?) + })?, + &self.state_mem.dep_kind, + ) + } -impl From for DepTypes { - fn from(node_dep_types: NodeDepTypes) -> Self { - Self { - ctx_ty: node_dep_types.ctx_ty, - dep_ty: None, + fn insert_dependant(&mut self, other: DepNode<'a>) -> bool { + let dep = other.state_mem.dep_mem.unwrap(); + if self.contains_member(dep) { + if self.state_mem.mem == dep { + self.depandants.push(Box::new(other)); + true + } else { + self.depandants + .iter_mut() + .find(|d| d.contains_member(dep)) + .unwrap() + .insert_dependant(other) + } + } else { + false + } + } + } + + // members need to be sorted so that members are updated after the members they depend on + let mut roots: Vec = vec![]; + for m in state_members.into_iter() { + if let Some(dep) = m.dep_mem { + let root_depends_on = roots + .iter() + .filter_map(|m| m.state_mem.dep_mem) + .any(|d| m.mem == d); + + if let Some(r) = roots.iter_mut().find(|r| r.contains_member(dep)) { + let new = DepNode::new(m); + if root_depends_on { + return Err(Error::new( + new.state_mem.mem.ident.span(), + format!("{} has a circular dependancy", new.state_mem.mem.ident), + )); + } + // return Err(Error::new(new.state_mem.mem.ident.span(), "stuff")); + r.insert_dependant(new); + continue; + } + } + let mut new = DepNode::new(m); + let mut i = 0; + while i < roots.len() { + if roots[i].state_mem.dep_mem == Some(new.state_mem.mem) { + let child = roots.remove(i); + new.insert_dependant(child); + } else { + i += 1; + } + } + roots.push(new); + } + let mut current_id = 0; + for r in &mut roots { + r.set_ids(&mut current_id); + } + if let Some(err) = roots.iter().find_map(DepNode::check) { + Err(err) + } else { + let state_members: Vec<_> = roots + .into_iter() + .map(|r| r.flatten().into_iter()) + .flatten() + .collect(); + + Ok(Self { state_members }) } } } +struct Dependancy { + ctx_ty: Option, + dep: Option, +} + +impl Parse for Dependancy { + fn parse(input: ParseStream) -> Result { + let dep = input + .parse() + .ok() + .filter(|i: &Ident| format!("{}", i) != "NONE"); + let comma: Option = input.parse().ok(); + let ctx_ty = input.parse().ok(); + Ok(Self { + ctx_ty: comma.and(ctx_ty), + dep, + }) + } +} + +#[derive(PartialEq, Debug)] struct Member { ty: Type, ident: Ident, @@ -280,16 +395,25 @@ impl Member { } } +#[derive(Debug, Clone)] struct StateMember<'a> { mem: &'a Member, dep_kind: DepKind, dep_mem: Option<&'a Member>, ctx_ty: Option, + dependants: Vec<(dioxus_native_core::state::MemberId, DepKind)>, + // This is just the index of the final order of the struct it is used to communicate which parts need updated and what order to update them in. + member_id: dioxus_native_core::state::MemberId, } impl<'a> StateMember<'a> { - fn parse(field: &Field, mem: &'a Member, parent: &'a Struct) -> Option> { - field.attrs.iter().find_map(|a| { + fn parse( + field: &Field, + mem: &'a Member, + parent: &'a Struct, + ) -> Result>> { + let mut err = Ok(()); + let member = field.attrs.iter().find_map(|a| { let dep_kind = a .path .get_ident() @@ -300,34 +424,44 @@ impl<'a> StateMember<'a> { _ => None, }) .flatten()?; - let deps: DepTypes = match dep_kind { - DepKind::NodeDepState => a.parse_args::().ok()?.into(), - _ => 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, - }) - }) + match a.parse_args::() { + Ok(dependancy) => { + let dep_mem = if let Some(name) = &dependancy.dep { + if let Some(found) = parent.members.iter().find(|m| &m.ident == name) { + Some(found) + } else { + err = Err(Error::new( + name.span(), + format!("{} not found in {}", name, parent.name), + )); + None + } + } else { + None + }; + Some(Self { + mem, + dep_kind, + dep_mem, + ctx_ty: dependancy.ctx_ty, + dependants: Vec::new(), + member_id: dioxus_native_core::state::MemberId(0), + }) + } + Err(e) => { + err = Err(e); + None + } + } + }); + err?; + Ok(member) } fn reduce_self(&self) -> quote::__private::TokenStream { let ident = &self.mem.ident; let get_ctx = if let Some(ctx_ty) = &self.ctx_ty { - if ctx_ty - == &Type::Tuple(TypeTuple { - paren_token: Paren { - span: quote::__private::Span::call_site(), - }, - elems: Punctuated::new(), - }) - { + if ctx_ty == &parse_quote!(()) { quote! {&()} } else { let msg = ctx_ty.to_token_stream().to_string() + " not found in context"; @@ -336,40 +470,119 @@ impl<'a> StateMember<'a> { } else { quote! {&()} }; + let states_changed = { + let child_dep = self + .dependants + .iter() + .filter(|(_, kind)| kind == &DepKind::ChildDepState) + .map(|(id, _)| id.0); + let parent_dep = self + .dependants + .iter() + .filter(|(_, kind)| kind == &DepKind::ParentDepState) + .map(|(id, _)| id.0); + let node_dep = self + .dependants + .iter() + .filter(|(_, kind)| kind == &DepKind::NodeDepState) + .map(|(id, _)| id.0); + match self.dep_kind { + DepKind::NodeDepState => { + quote! { + dioxus_native_core::state::NodeStatesChanged{ + node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], + } + } + } + DepKind::ChildDepState => { + quote! { + dioxus_native_core::state::ChildStatesChanged{ + node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], + child_dep: &[#(dioxus_native_core::state::MemberId(#child_dep), )*], + } + } + } + DepKind::ParentDepState => { + quote! { + dioxus_native_core::state::ParentStatesChanged{ + node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], + parent_dep: &[#(dioxus_native_core::state::MemberId(#parent_dep), )*], + } + } + } + } + }; + let ty = &self.mem.ty; - let node_view = quote!(NodeView::new(node, #ty::NODE_MASK, vdom)); + let node_view = + quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK, vdom)); + let id = self.member_id.0; if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) { match self.dep_kind { DepKind::NodeDepState => { - quote!(self.#ident.reduce(#node_view, #get_ctx)) + quote!({ + // println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); + if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){ + Some(#states_changed) + } else{ + None + } + }) } DepKind::ChildDepState => { - quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx)) + quote!({ + // println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); + if self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx){ + Some(#states_changed) + } else{ + None + } + }) } DepKind::ParentDepState => { - quote!(self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx)) + quote!({ + // println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); + if self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx){ + Some(#states_changed) + } else{ + None + } + }) } } } else { match self.dep_kind { DepKind::NodeDepState => { - quote!(self.#ident.reduce(#node_view, #get_ctx)) + quote!({ + // println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); + if self.#ident.reduce(#node_view, &(), #get_ctx){ + Some(#states_changed) + } else{ + None + } + }) } DepKind::ChildDepState => { - quote!(self.#ident.reduce(#node_view, &(), #get_ctx)) + quote!({ + // println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); + if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){ + Some(#states_changed) + } else{ + None + } + }) } DepKind::ParentDepState => { - quote!(self.#ident.reduce(#node_view, Some(&()), #get_ctx)) + quote!({ + println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); + if self.#ident.reduce(#node_view, Some(&()), #get_ctx){ + Some(#states_changed) + } else{ + None + } + }) } } } } - - fn type_id(&self) -> quote::__private::TokenStream { - let ty = &self.mem.ty; - quote!({ - let type_id = std::any::TypeId::of::<#ty>(); - type_id - }) - } } diff --git a/packages/native-core-macro/src/sorted_slice.rs b/packages/native-core-macro/src/sorted_slice.rs new file mode 100644 index 000000000..fa975767c --- /dev/null +++ b/packages/native-core-macro/src/sorted_slice.rs @@ -0,0 +1,28 @@ +extern crate proc_macro; + +use std::collections::BTreeMap; + +use syn::{ + self, bracketed, + parse::{Parse, ParseStream, Result}, + LitStr, Token, +}; +pub struct StrSlice { + pub map: BTreeMap, +} + +impl Parse for StrSlice { + fn parse(input: ParseStream) -> Result { + let content; + bracketed!(content in input); + let mut map = BTreeMap::new(); + while let Ok(s) = content.parse::() { + map.insert(s.value(), s); + #[allow(unused_must_use)] + { + content.parse::(); + } + } + Ok(StrSlice { map }) + } +} diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core-macro/tests/change_nodes.rs similarity index 100% rename from packages/native-core/tests/change_nodes.rs rename to packages/native-core-macro/tests/change_nodes.rs diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core-macro/tests/initial_build.rs similarity index 99% rename from packages/native-core/tests/initial_build.rs rename to packages/native-core-macro/tests/initial_build.rs index 750987a29..e6bd651e2 100644 --- a/packages/native-core/tests/initial_build.rs +++ b/packages/native-core-macro/tests/initial_build.rs @@ -8,7 +8,7 @@ use dioxus_native_core::real_dom::RealDom; use dioxus_native_core::state::State; use dioxus_native_core_macro::State; -#[derive(State, Default, Clone)] +#[derive(Default, Clone, State)] struct Empty {} #[test] diff --git a/packages/native-core-macro/tests/parse.rs b/packages/native-core-macro/tests/parse.rs new file mode 100644 index 000000000..6103015e7 --- /dev/null +++ b/packages/native-core-macro/tests/parse.rs @@ -0,0 +1,74 @@ +use dioxus_native_core::node_ref::*; +use dioxus_native_core::state::*; +use dioxus_native_core_macro::*; + +#[derive(State, Default, Clone)] +#[allow(dead_code)] +struct Z { + // depends on text, the C component of it's parent and a u16 context + #[parent_dep_state(c, u16)] + d: D, + // depends on just attributes and no context + #[node_dep_state()] + a: A, + // depends on the B component of children and i32 context + #[child_dep_state(b, i32)] + b: B, + // depends on the C component of it's parent and a u8 context + #[parent_dep_state(c, u8)] + c: C, + // this will remain uneffected on updates + n: i32, +} + +use dioxus_native_core::state::NodeDepState; + +#[derive(Default, Clone, Debug)] +struct A; +impl NodeDepState for A { + type Ctx = (); + type DepState = (); + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, false, false, false); + fn reduce(&mut self, _: NodeView, _: &Self::DepState, _: &()) -> bool { + todo!() + } +} + +#[derive(Default, Clone, Debug)] +struct B; +impl ChildDepState for B { + type Ctx = i32; + type DepState = Self; + fn reduce<'a>( + &mut self, + _: NodeView, + _: impl Iterator, + _: &i32, + ) -> bool + where + Self::DepState: 'a, + { + todo!() + } +} + +#[derive(Default, Clone, Debug)] +struct C; +impl ParentDepState for C { + type Ctx = u8; + type DepState = Self; + fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &u8) -> bool { + todo!() + } +} + +#[derive(Default, Clone, Debug)] +struct D; +impl ParentDepState for D { + type Ctx = u16; + type DepState = C; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false, true); + fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &u16) -> bool { + todo!() + } +} diff --git a/packages/native-core-macro/tests/test.rs b/packages/native-core-macro/tests/test.rs new file mode 100644 index 000000000..ea0231cd7 --- /dev/null +++ b/packages/native-core-macro/tests/test.rs @@ -0,0 +1,70 @@ +use dioxus_native_core::node_ref::*; +use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; +use dioxus_native_core_macro::State; +#[derive(Debug, Clone, PartialEq, Default)] +struct BubbledUpStateTester(Option, Vec>); +impl ChildDepState for BubbledUpStateTester { + type Ctx = u32; + type DepState = Self; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false, false); + 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()), + children.into_iter().map(|c| Box::new(c.clone())).collect(), + ); + true + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +struct PushedDownStateTester(Option, Option>); +impl ParentDepState for PushedDownStateTester { + type Ctx = u32; + type DepState = Self; + 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( + node.tag().map(|s| s.to_string()), + parent.map(|c| Box::new(c.clone())), + ); + true + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +struct NodeStateTester(Option, Vec<(String, String)>); +impl NodeDepState for NodeStateTester { + type Ctx = u32; + type DepState = (); + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false, false); + fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, 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 + } +} + +#[derive(State, Clone, Default, Debug)] +struct StateTester { + #[child_dep_state(bubbled, u32)] + bubbled: BubbledUpStateTester, + #[parent_dep_state(pushed, u32)] + pushed: PushedDownStateTester, + #[node_dep_state(NONE, u32)] + node: NodeStateTester, +} diff --git a/packages/native-core/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs similarity index 75% rename from packages/native-core/tests/update_state.rs rename to packages/native-core-macro/tests/update_state.rs index d4825fdd5..d6e5e22b7 100644 --- a/packages/native-core/tests/update_state.rs +++ b/packages/native-core-macro/tests/update_state.rs @@ -3,17 +3,16 @@ use dioxus_core::VNode; use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; +use dioxus_native_core::node_ref::*; use dioxus_native_core::real_dom::*; -use dioxus_native_core::state::{ - AttributeMask, ChildDepState, NodeDepState, NodeMask, NodeView, ParentDepState, State, -}; +use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; use dioxus_native_core_macro::State; #[derive(Debug, Clone, Default, State)] struct CallCounterState { - #[child_dep_state(ChildDepCallCounter)] + #[child_dep_state(child_counter)] child_counter: ChildDepCallCounter, - #[parent_dep_state(ParentDepCallCounter)] + #[parent_dep_state(parent_counter)] parent_counter: ParentDepCallCounter, #[node_dep_state()] node_counter: NodeDepCallCounter, @@ -60,8 +59,9 @@ impl ParentDepState for ParentDepCallCounter { struct NodeDepCallCounter(u32); impl NodeDepState for NodeDepCallCounter { type Ctx = (); + type DepState = (); const NODE_MASK: NodeMask = NodeMask::ALL; - fn reduce(&mut self, _node: NodeView, _ctx: &Self::Ctx) -> bool { + fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { self.0 += 1; true } @@ -107,22 +107,13 @@ impl ParentDepState for PushedDownStateTester { } } -#[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; + type DepState = (); const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false, false); - fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool { + fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = NodeStateTester( node.tag().map(|s| s.to_string()), @@ -134,6 +125,16 @@ impl NodeDepState for NodeStateTester { } } +#[derive(State, Clone, Default, Debug)] +struct StateTester { + #[child_dep_state(bubbled, u32)] + bubbled: BubbledUpStateTester, + #[parent_dep_state(pushed, u32)] + pushed: PushedDownStateTester, + #[node_dep_state(NONE, u32)] + node: NodeStateTester, +} + #[test] fn state_initial() { #[allow(non_snake_case)] @@ -172,7 +173,10 @@ fn state_initial() { ] ); assert_eq!(root_div.state.pushed.0, Some("div".to_string())); - assert_eq!(root_div.state.pushed.1, None); + assert_eq!( + root_div.state.pushed.1, + Some(Box::new(PushedDownStateTester(None, None))) + ); assert_eq!(root_div.state.node.0, Some("div".to_string())); assert_eq!(root_div.state.node.1, vec![]); @@ -184,7 +188,7 @@ fn state_initial() { child_p.state.pushed.1, Some(Box::new(PushedDownStateTester( Some("div".to_string()), - None + Some(Box::new(PushedDownStateTester(None, None))) ))) ); assert_eq!(child_p.state.node.0, Some("p".to_string())); @@ -201,7 +205,7 @@ fn state_initial() { child_h1.state.pushed.1, Some(Box::new(PushedDownStateTester( Some("div".to_string()), - None + Some(Box::new(PushedDownStateTester(None, None))) ))) ); assert_eq!(child_h1.state.node.0, Some("h1".to_string())); @@ -256,10 +260,6 @@ 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); @@ -268,7 +268,7 @@ fn state_reduce_initally_called_minimally() { } #[test] -fn state_reduce_down_called_minimally_on_update() { +fn state_reduce_parent_called_minimally_on_update() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { @@ -329,21 +329,20 @@ 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); }); - // panic!() } #[test] -fn state_reduce_up_called_minimally_on_update() { +fn state_reduce_child_called_minimally_on_update() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { - width: "100%", div{ div{ - p{} + p{ + width: "100%", + } } p{ "hello" @@ -362,10 +361,11 @@ fn state_reduce_up_called_minimally_on_update() { let mutations = vdom.create_vnodes(rsx! { div { - width: "100%", div{ div{ - p{} + p{ + width: "100%", + } } p{ "hello" @@ -397,6 +397,93 @@ 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); assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 }); }); } + +#[derive(Debug, Clone, Default, State)] +struct UnorderedDependanciesState { + #[node_dep_state(c)] + b: BDepCallCounter, + #[node_dep_state()] + c: CDepCallCounter, + #[node_dep_state(b)] + a: ADepCallCounter, +} + +#[derive(Debug, Clone, Default, PartialEq)] +struct ADepCallCounter(usize, BDepCallCounter); +impl NodeDepState for ADepCallCounter { + type Ctx = (); + type DepState = BDepCallCounter; + const NODE_MASK: NodeMask = NodeMask::NONE; + fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { + self.0 += 1; + self.1 = sibling.clone(); + true + } +} + +#[derive(Debug, Clone, Default, PartialEq)] +struct BDepCallCounter(usize, CDepCallCounter); +impl NodeDepState for BDepCallCounter { + type Ctx = (); + type DepState = CDepCallCounter; + const NODE_MASK: NodeMask = NodeMask::NONE; + fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { + self.0 += 1; + self.1 = sibling.clone(); + true + } +} + +#[derive(Debug, Clone, Default, PartialEq)] +struct CDepCallCounter(usize); +impl NodeDepState for CDepCallCounter { + type Ctx = (); + type DepState = (); + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { + self.0 += 1; + true + } +} + +#[test] +fn dependancies_order_independant() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div { + width: "100%", + p{ + "hello" + } + }) + } + + let vdom = VirtualDom::new(Base); + + let mutations = vdom.create_vnodes(rsx! { + div { + width: "100%", + p{ + "hello" + } + } + }); + + let mut dom: RealDom = RealDom::new(); + + let nodes_updated = dom.apply_mutations(vec![mutations]); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); + + let c = CDepCallCounter(1); + let b = BDepCallCounter(1, c.clone()); + let a = ADepCallCounter(1, b.clone()); + dom.traverse_depth_first(|n| { + assert_eq!(&n.state.a, &a); + assert_eq!(&n.state.b, &b); + assert_eq!(&n.state.c, &c); + }); +} diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 3e37d7e8a..ab0e4f6f8 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -10,7 +10,6 @@ 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" diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 7692a5ae1..a6e011fa9 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 node_ref; pub mod real_dom; pub mod state; -pub use dioxus_native_core_macro; diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs new file mode 100644 index 000000000..bed0dc9ff --- /dev/null +++ b/packages/native-core/src/node_ref.rs @@ -0,0 +1,212 @@ +use dioxus_core::*; + +use crate::state::union_ordered_iter; + +#[derive(Debug)] +pub struct NodeView<'a> { + inner: &'a VNode<'a>, + mask: NodeMask, +} +impl<'a> NodeView<'a> { + pub fn new(mut vnode: &'a VNode<'a>, view: NodeMask, vdom: &'a VirtualDom) -> Self { + if let VNode::Component(sc) = vnode { + let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap(); + vnode = scope.root_node(); + } + Self { + inner: vnode, + mask: view, + } + } + + pub fn id(&self) -> ElementId { + self.inner.mounted_id() + } + + pub fn tag(&self) -> Option<&'a str> { + self.mask.tag.then(|| self.el().map(|el| el.tag)).flatten() + } + + pub fn namespace(&self) -> Option<&'a str> { + 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 + } + } + + fn txt(&self) -> Option<&'a VText<'a>> { + if let VNode::Text(txt) = &self.inner { + Some(txt) + } else { + None + } + } +} + +#[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.binary_search(&attr).is_ok(), + AttributeMask::Static(l) => l.binary_search(&attr).is_ok(), + } + } + + 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 { + let new = match (self, other) { + (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(); + 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: AttributeMask, + tag: bool, + namespace: bool, + text: bool, +} + +impl NodeMask { + 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, 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.overlaps(&other.attritutes) + || (self.text && other.text) + } + + 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, + } + } +} diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index ecb165c26..496af5797 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,14 +1,17 @@ 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::state::{union_ordered_iter, AttributeMask, NodeMask, State}; +use crate::state::{union_ordered_iter, State}; +use crate::{ + node_ref::{AttributeMask, NodeMask}, + state::MemberId, +}; /// 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. @@ -211,7 +214,7 @@ impl RealDom { #[derive(PartialEq, Clone, Debug)] enum StatesToCheck { All, - Some(Vec), + Some(Vec), } impl StatesToCheck { fn union(&self, other: &Self) -> Self { @@ -294,14 +297,28 @@ impl RealDom { for node_ref in &nodes_updated { let mut changed = false; let node = &mut self[node_ref.id]; - let ids = match &node_ref.to_check { + let mut ids = match &node_ref.to_check { StatesToCheck::All => node.state.node_dep_types(&node_ref.node_mask), + // this should only be triggered from the current node, so all members will need to be checked StatesToCheck::Some(_) => unreachable!(), }; - for ty in ids { + let mut i = 0; + while i < ids.len() { + let id = ids[i]; let node = &mut self[node_ref.id]; let vnode = node.element(vdom); - changed |= node.state.update_node_dep_state(ty, vnode, vdom, &ctx); + if let Some(members_effected) = + node.state.update_node_dep_state(id, vnode, vdom, &ctx) + { + debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); + for m in members_effected.node_dep { + if let Err(idx) = ids.binary_search(m) { + ids.insert(idx, *m); + } + } + changed = true; + } + i += 1; } if changed { to_rerender.insert(node_ref.id); @@ -319,19 +336,30 @@ impl RealDom { } = 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 { + let mut ids = match to_check { StatesToCheck::All => node.state.child_dep_types(&node_mask), StatesToCheck::Some(ids) => ids, }; let mut changed = Vec::new(); - for ty in ids { + let mut i = 0; + while i < ids.len() { + let id = ids[i]; let vnode = node.element(vdom); - if node - .state - .update_child_dep_state(ty, vnode, vdom, &children_state, &ctx) + if let Some(members_effected) = + node.state + .update_child_dep_state(id, vnode, vdom, &children_state, &ctx) { - changed.push(ty); + debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); + for m in members_effected.node_dep { + if let Err(idx) = ids.binary_search(m) { + ids.insert(idx, *m); + } + } + for m in members_effected.child_dep { + changed.push(*m); + } } + i += 1; } if let Some(parent_id) = node.parent { if !changed.is_empty() { @@ -374,19 +402,32 @@ impl RealDom { to_check, } = node_ref; let node = &self[id]; - let ids = match to_check { + let mut ids = match to_check { StatesToCheck::All => node.state.parent_dep_types(&node_mask), StatesToCheck::Some(ids) => ids, }; let mut changed = Vec::new(); let (node, parent) = self.get_node_parent_mut(id).unwrap(); - for ty in ids { + let mut i = 0; + while i < ids.len() { + let id = ids[i]; let vnode = node.element(vdom); let parent = parent.as_deref(); let state = &mut node.state; - if state.update_parent_dep_state(ty, vnode, vdom, parent.map(|n| &n.state), &ctx) { - changed.push(ty); + if let Some(members_effected) = + state.update_parent_dep_state(id, vnode, vdom, parent.map(|n| &n.state), &ctx) + { + debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); + for m in members_effected.node_dep { + if let Err(idx) = ids.binary_search(m) { + ids.insert(idx, *m); + } + } + for m in members_effected.parent_dep { + changed.push(*m); + } } + i += 1; } to_rerender.insert(id); diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 13397837a..5febd0d2c 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,7 +1,9 @@ -use std::{any::TypeId, fmt::Debug}; +use std::fmt::Debug; use anymap::AnyMap; -use dioxus_core::{Attribute, ElementId, VElement, VNode, VText, VirtualDom}; +use dioxus_core::VNode; + +use crate::node_ref::{NodeMask, NodeView}; pub(crate) fn union_ordered_iter( s_iter: impl Iterator, @@ -34,215 +36,6 @@ pub(crate) fn union_ordered_iter( v } -#[derive(Debug)] -pub struct NodeView<'a> { - inner: &'a VNode<'a>, - mask: NodeMask, -} -impl<'a> NodeView<'a> { - pub fn new(mut vnode: &'a VNode<'a>, view: NodeMask, vdom: &'a VirtualDom) -> Self { - if let VNode::Component(sc) = vnode { - let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap(); - vnode = scope.root_node(); - } - Self { - inner: vnode, - mask: view, - } - } - - pub fn id(&self) -> ElementId { - self.inner.mounted_id() - } - - pub fn tag(&self) -> Option<&'a str> { - self.mask.tag.then(|| self.el().map(|el| el.tag)).flatten() - } - - pub fn namespace(&self) -> Option<&'a str> { - 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 - } - } - - fn txt(&self) -> Option<&'a VText<'a>> { - if let VNode::Text(txt) = &self.inner { - Some(txt) - } else { - None - } - } -} - -#[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.binary_search(&attr).is_ok(), - AttributeMask::Static(l) => l.binary_search(&attr).is_ok(), - } - } - - 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 { - let new = match (self, other) { - (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(); - 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: AttributeMask, - tag: bool, - namespace: bool, - text: bool, -} - -impl NodeMask { - 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, 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.overlaps(&other.attritutes) - || (self.text && other.text) - } - - 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, - } - } -} - /// 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. @@ -250,7 +43,8 @@ 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; + /// This must be either a [ChildDepState] or [NodeDepState] + type DepState; const NODE_MASK: NodeMask = NodeMask::NONE; fn reduce<'a>( &mut self, @@ -269,7 +63,8 @@ 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; + /// This must be either a [ParentDepState] or [NodeDepState] + type DepState; const NODE_MASK: NodeMask = NodeMask::NONE; fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool; } @@ -279,40 +74,93 @@ pub trait ParentDepState { /// Called at most once per update. pub trait NodeDepState { type Ctx; + type DepState: NodeDepState; const NODE_MASK: NodeMask = NodeMask::NONE; - fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool; + fn reduce(&mut self, node: NodeView, sibling: &Self::DepState, ctx: &Self::Ctx) -> bool; +} + +#[derive(Debug)] +pub struct ChildStatesChanged { + pub node_dep: &'static [MemberId], + pub child_dep: &'static [MemberId], +} + +#[derive(Debug)] +pub struct ParentStatesChanged { + pub node_dep: &'static [MemberId], + pub parent_dep: &'static [MemberId], +} + +#[derive(Debug)] +pub struct NodeStatesChanged { + pub node_dep: &'static [MemberId], } pub trait State: Default + Clone { fn update_node_dep_state<'a>( &'a mut self, - ty: TypeId, + ty: MemberId, node: &'a VNode<'a>, vdom: &'a dioxus_core::VirtualDom, ctx: &AnyMap, - ) -> bool; + ) -> Option; /// This must be a valid resolution order. (no nodes updated before a state they rely on) - fn child_dep_types(&self, mask: &NodeMask) -> Vec; + fn child_dep_types(&self, mask: &NodeMask) -> Vec; fn update_parent_dep_state<'a>( &'a mut self, - ty: TypeId, + ty: MemberId, node: &'a VNode<'a>, vdom: &'a dioxus_core::VirtualDom, parent: Option<&Self>, ctx: &AnyMap, - ) -> bool; + ) -> Option; /// This must be a valid resolution order. (no nodes updated before a state they rely on) - fn parent_dep_types(&self, mask: &NodeMask) -> Vec; + fn parent_dep_types(&self, mask: &NodeMask) -> Vec; fn update_child_dep_state<'a>( &'a mut self, - ty: TypeId, + ty: MemberId, node: &'a VNode<'a>, vdom: &'a dioxus_core::VirtualDom, children: &[&Self], ctx: &AnyMap, - ) -> bool; + ) -> Option; /// This must be a valid resolution order. (no nodes updated before a state they rely on) - fn node_dep_types(&self, mask: &NodeMask) -> Vec; + fn node_dep_types(&self, mask: &NodeMask) -> Vec; } + +impl ChildDepState for () { + type Ctx = (); + type DepState = (); + fn reduce<'a>( + &mut self, + _: NodeView, + _: impl Iterator, + _: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { + false + } +} + +impl ParentDepState for () { + type Ctx = (); + type DepState = (); + fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &Self::Ctx) -> bool { + false + } +} + +impl NodeDepState for () { + type Ctx = (); + type DepState = (); + fn reduce(&mut self, _: NodeView, _sibling: &Self::DepState, _: &Self::Ctx) -> bool { + false + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct MemberId(pub usize); diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs deleted file mode 100644 index a035faf4b..000000000 --- a/packages/native-core/tests/parse.rs +++ /dev/null @@ -1,59 +0,0 @@ -use dioxus_native_core::state::*; -use dioxus_native_core_macro::*; - -#[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(B, i32)] - y: B, - // depends on attributes, the C component of it's parent and a u8 context - #[parent_dep_state(C, u8)] - z: C, -} - -use dioxus_native_core::state::NodeDepState; - -#[derive(Default, Clone)] -struct A; -impl NodeDepState for A { - type Ctx = (); - fn reduce(&mut self, _: NodeView, _: &()) -> bool { - todo!() - } -} - -#[derive(Default, Clone)] -struct B; -impl ChildDepState for B { - type Ctx = i32; - type DepState = Self; - fn reduce<'a>( - &mut self, - _: dioxus_native_core::state::NodeView, - _: impl Iterator, - _: &i32, - ) -> bool - where - Self::DepState: 'a, - { - todo!() - } -} - -#[derive(Default, Clone)] -struct C; -impl ParentDepState for C { - type Ctx = u8; - type DepState = Self; - fn reduce( - &mut self, - _: dioxus_native_core::state::NodeView, - _: Option<&Self::DepState>, - _: &u8, - ) -> bool { - todo!() - } -} diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 43064bb63..58d71db66 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -16,6 +16,7 @@ license = "MIT/Apache-2.0" dioxus-core = { path = "../core", version = "^0.2.0" } dioxus-html = { path = "../html", version = "^0.2.0" } dioxus-native-core = { path = "../native-core", version = "^0.2.0" } +dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" } tui = "0.17.0" crossterm = "0.23.0" diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index de4f13569..45e279f45 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -2,9 +2,10 @@ use std::cell::RefCell; use std::rc::Rc; use dioxus_core::*; -use dioxus_native_core::dioxus_native_core_macro::sorted_str_slice; use dioxus_native_core::layout_attributes::apply_layout_attributes; -use dioxus_native_core::state::{AttributeMask, ChildDepState, NodeMask, NodeView}; +use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView}; +use dioxus_native_core::state::ChildDepState; +use dioxus_native_core_macro::sorted_str_slice; use stretch2::prelude::*; #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 1eb3f7612..91e75e8db 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -7,7 +7,8 @@ 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, state::*}; +use dioxus_native_core::{real_dom::RealDom, state::*}; +use dioxus_native_core_macro::State; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, pin_mut, StreamExt, @@ -36,10 +37,10 @@ type Node = dioxus_native_core::real_dom::Node; #[derive(Debug, Clone, State, Default)] struct NodeState { - #[child_dep_state(StretchLayout, RefCell)] + #[child_dep_state(layout, RefCell)] layout: StretchLayout, // depends on attributes, the C component of it's parent and a u8 context - #[parent_dep_state(StyleModifier)] + #[parent_dep_state(style)] style: StyleModifier, } diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index b676d3409..7bd228f8b 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -32,8 +32,10 @@ use dioxus_core::Attribute; use dioxus_native_core::{ layout_attributes::{parse_value, UnitSystem}, - state::{AttributeMask, NodeMask, NodeView, ParentDepState}, + node_ref::{AttributeMask, NodeMask, NodeView}, + state::ParentDepState, }; +use dioxus_native_core_macro::sorted_str_slice; use crate::style::{RinkColor, RinkStyle}; @@ -47,7 +49,8 @@ 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); + const NODE_MASK: NodeMask = + NodeMask::new(AttributeMask::Static(SORTED_STYLE_ATTRS), true, true, false); fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool { let mut new = StyleModifier::default(); @@ -586,3 +589,211 @@ fn apply_text(name: &str, value: &str, style: &mut StyleModifier) { fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifier) { todo!() } + +const SORTED_STYLE_ATTRS: &'static [&'static str] = &sorted_str_slice!([ + "animation", + "animation-delay", + "animation-direction", + "animation-duration", + "animation-fill-mode", + "animation-iteration-count", + "animation-name", + "animation-play-state", + "animation-timing-function", + "backface-visibility", + "background", + "background-attachment", + "background-clip", + "background-color", + "background-image", + "background-origin", + "background-position", + "background-repeat", + "background-size", + "border", + "border-bottom", + "border-bottom-color", + "border-bottom-left-radius", + "border-bottom-right-radius", + "border-bottom-style", + "border-bottom-width", + "border-collapse", + "border-color", + "border-image", + "border-image-outset", + "border-image-repeat", + "border-image-slice", + "border-image-source", + "border-image-width", + "border-left", + "border-left-color", + "border-left-style", + "border-left-width", + "border-radius", + "border-right", + "border-right-color", + "border-right-style", + "border-right-width", + "border-spacing", + "border-style", + "border-top", + "border-top-color", + "border-top-left-radius", + "border-top-right-radius", + "border-top-style", + "border-top-width", + "border-width", + "bottom", + "box-shadow", + "box-sizing", + "caption-side", + "clear", + "clip", + "color", + "columns", + "content", + "counter-increment", + "counter-reset", + "cursor", + "empty-cells", + "float", + "font", + "font-family", + "font-size", + "font-size-adjust", + "font-stretch", + "font-style", + "font-variant", + "font-weight", + "letter-spacing", + "line-height", + "list-style", + "list-style-image", + "list-style-position", + "list-style-type", + "opacity", + "order", + "outline", + "outline-color", + "outline-offset", + "outline-style", + "outline-width", + "page-break-after", + "page-break-before", + "page-break-inside", + "perspective", + "perspective-origin", + "pointer-events", + "quotes", + "resize", + "tab-size", + "table-layout", + "text-align", + "text-align-last", + "text-decoration", + "text-decoration-color", + "text-decoration-line", + "text-decoration-style", + "text-indent", + "text-justify", + "text-overflow", + "text-shadow", + "text-transform", + "transition", + "transition-delay", + "transition-duration", + "transition-property", + "transition-timing-function", + "visibility", + "white-space", + "background-color", + "background", + "background-attachment", + "background-clip", + "background-image", + "background-origin", + "background-position", + "background-repeat", + "background-size", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset", + "none", + "hidden", + "border", + "border-bottom", + "border-bottom-color", + "border-bottom-left-radius", + "border-bottom-right-radius", + "border-bottom-style", + "border-bottom-width", + "border-collapse", + "border-color", + " ", + " ", + "border-image", + "border-image-outset", + "border-image-repeat", + "border-image-slice", + "border-image-source", + "border-image-width", + "border-left", + "border-left-color", + "border-left-style", + "border-left-width", + "border-radius", + " ", + "border-right", + "border-right-color", + "border-right-style", + "border-right-width", + "border-spacing", + "border-style", + "border-top", + "border-top-color", + "border-top-left-radius", + "border-top-right-radius", + "border-top-style", + "border-top-width", + "border-width", + " ", + "animation", + "animation-delay", + "animation-direction", + "animation-duration", + "animation-fill-mode", + "animation-itera ", + "animation-name", + "animation-play-state", + "animation-timing-function", + "font", + "font-family", + "font-size", + "font-size-adjust", + "font-stretch", + "font-style", + "italic", + "oblique", + "font-variant", + "font-weight", + "bold", + "normal", + "text-align", + "text-align-last", + "text-decoration", + "text-decoration-line", + "line-through", + "underline", + "text-decoration-color", + "text-decoration-style", + "text-indent", + "text-justify", + "text-overflow", + "text-shadow", + "text-transform" +]);