From 7c30d93a3d016877b7901c5b999a66673fa044d5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 19 Apr 2022 20:12:57 -0500 Subject: [PATCH] nested state --- docs/reference/src/guide/custom_renderer.md | 44 ++++- packages/native-core-macro/src/lib.rs | 152 ++++++++++++++++-- .../native-core-macro/tests/update_state.rs | 44 +++++ packages/native-core/src/real_dom.rs | 14 +- packages/native-core/src/state.rs | 46 +++++- 5 files changed, 270 insertions(+), 30 deletions(-) diff --git a/docs/reference/src/guide/custom_renderer.md b/docs/reference/src/guide/custom_renderer.md index b60eba8ac..9a73dd7f7 100644 --- a/docs/reference/src/guide/custom_renderer.md +++ b/docs/reference/src/guide/custom_renderer.md @@ -304,7 +304,7 @@ flowchart TB end ``` -To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState. +To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState and a RealDom struct. ```rust use dioxus_native_core::node_ref::*; @@ -436,6 +436,48 @@ struct ToyState { } ``` +Now that we have our state, we can put it to use in our dom. Re can update the dom with update_state to update the structure of the dom (adding, removing, and chaning properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed. +```rust +fn main(){ + fn app(cx: Scope) -> Element { + cx.render(rsx!{ + div{ + color: "red", + "hello world" + } + }) + } + let vdom = VirtualDom::new(app); + let rdom: RealDom = RealDom::new(); + + let mutations = dom.rebuild(); + // update the structure of the real_dom tree + let to_update = rdom.apply_mutations(vec![mutations]); + let mut ctx = AnyMap::new(); + // set the font size to 3.3 + ctx.insert(3.3); + // update the ToyState for nodes in the real_dom tree + let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap(); + + // we need to run the vdom in a async runtime + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()? + .block_on(async { + loop{ + let wait = vdom.wait_for_work(); + let mutations = vdom.work_with_deadline(|| false); + let to_update = rdom.apply_mutations(mutations); + let mut ctx = AnyMap::new(); + ctx.insert(3.3); + let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap(); + + // render... + } + }) +} +``` + ## Layout For most platforms the layout of the Elements will stay the same. The layout_attributes module provides a way to apply html attributes to a stretch layout style. diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 61d03b545..d645a2398 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -4,6 +4,7 @@ mod sorted_slice; use dioxus_native_core::state::MemberId; use proc_macro::TokenStream; +use quote::format_ident; use quote::{quote, ToTokens, __private::Span}; use sorted_slice::StrSlice; use syn::{ @@ -26,7 +27,10 @@ enum DepKind { Parent, } -#[proc_macro_derive(State, attributes(node_dep_state, child_dep_state, parent_dep_state))] +#[proc_macro_derive( + State, + attributes(node_dep_state, child_dep_state, parent_dep_state, state) +)] pub fn state_macro_derive(input: TokenStream) -> TokenStream { let ast = syn::parse(input).unwrap(); impl_derive_macro(&ast) @@ -89,8 +93,51 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let type_name_str = type_name.to_string(); + let child_states = &state_strct.child_states; + + let member_size = state_strct.state_members.len(); + + let child_state_ty = child_states.iter().map(|m| &m.ty); + let child_state_idents: Vec<_> = child_states.iter().map(|m| &m.ident).collect(); + let sum_const_declarations = child_state_ty.clone().enumerate().map(|(i, ty)| { + let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string()); + let ident_minus = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string()); + if i == 0 { + quote!(const #ident_minus: usize = #member_size + #ty::SIZE - 1; + const #ident: usize = #member_size + #ty::SIZE;) + } else { + let prev_ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string()); + quote!(const #ident_minus: usize = #prev_ident + #ty::SIZE - 1; + const #ident: usize = #prev_ident + #ty::SIZE;) + } + }); + let sum_idents: Vec<_> = std::iter::once(quote!(#member_size)) + .chain((0..child_states.len()).map(|i| { + let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string()); + quote!(#ident) + })) + .collect(); + + let child_state_ranges: Vec<_> = (0..child_state_ty.len()) + .map(|i| { + let current = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string()); + let previous = if i == 0 { + quote!(#member_size) + } else { + let ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string()); + quote!(#ident) + }; + quote!(#previous..=#current) + }) + .collect(); + let gen = quote! { + #( + #sum_const_declarations + )* impl State for #type_name{ + const SIZE: usize = #member_size #( + #child_state_ty::SIZE)*; + fn update_node_dep_state<'a>( &'a mut self, ty: dioxus_native_core::state::MemberId, @@ -99,10 +146,26 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { ctx: &anymap::AnyMap, ) -> Option{ use dioxus_native_core::state::NodeDepState as _; + use dioxus_native_core::state::State as _; match ty.0{ #( #node_ids => #node_dep_state_fields, )* + #( + #child_state_ranges => { + self.#child_state_idents.update_node_dep_state( + ty - #sum_idents, + node, + vdom, + ctx, + ).map(|mut changed|{ + for id in &mut changed.node_dep{ + *id += #sum_idents; + } + changed + }) + } + )* _ => panic!("{:?} not in {}", ty, #type_name_str), } } @@ -120,6 +183,25 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { #( #parent_ids => #parent_dep_state_fields, )* + #( + #child_state_ranges => { + self.#child_state_idents.update_parent_dep_state( + ty - #sum_idents, + node, + vdom, + parent.map(|p| &p.#child_state_idents), + ctx, + ).map(|mut changed|{ + for id in &mut changed.node_dep{ + *id += #sum_idents; + } + for id in &mut changed.parent_dep{ + *id += #sum_idents; + } + changed + }) + } + )* _ => panic!("{:?} not in {}", ty, #type_name_str), } } @@ -129,13 +211,32 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { ty: dioxus_native_core::state::MemberId, node: &'a dioxus_core::VNode<'a>, vdom: &'a dioxus_core::VirtualDom, - children: &[&Self], + children: &Vec<&Self>, ctx: &anymap::AnyMap, ) -> Option{ use dioxus_native_core::state::ChildDepState as _; match ty.0{ #( - #child_ids => {#child_dep_state_fields}, + #child_ids => #child_dep_state_fields, + )* + #( + #child_state_ranges => { + self.#child_state_idents.update_child_dep_state( + ty - #sum_idents, + node, + vdom, + &children.iter().map(|p| &p.#child_state_idents).collect(), + ctx, + ).map(|mut changed|{ + for id in &mut changed.node_dep{ + *id += #sum_idents; + } + for id in &mut changed.child_dep{ + *id += #sum_idents; + } + changed + }) + } )* _ => panic!("{:?} not in {}", ty, #type_name_str), } @@ -146,6 +247,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { #(if #child_types::NODE_MASK.overlaps(mask) { dep_types.push(dioxus_native_core::state::MemberId(#child_ids_clone)); })* + #( + dep_types.extend(self.#child_state_idents.child_dep_types(mask).into_iter().map(|id| id + #sum_idents)); + )* dep_types } @@ -154,6 +258,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { #(if #parent_types::NODE_MASK.overlaps(mask) { dep_types.push(dioxus_native_core::state::MemberId(#parent_ids_clone)); })* + #( + dep_types.extend(self.#child_state_idents.parent_dep_types(mask).into_iter().map(|id| id + #sum_idents)); + )* dep_types } @@ -162,6 +269,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { #(if #node_types::NODE_MASK.overlaps(mask) { dep_types.push(dioxus_native_core::state::MemberId(#node_ids_clone)); })* + #( + dep_types.extend(self.#child_state_idents.node_dep_types(mask).into_iter().map(|id| id + #sum_idents)); + )* dep_types } } @@ -186,6 +296,7 @@ impl Struct { struct StateStruct<'a> { state_members: Vec>, + child_states: Vec<&'a Member>, } impl<'a> StateStruct<'a> { @@ -203,6 +314,20 @@ impl<'a> StateStruct<'a> { } }); + let child_states = strct + .members + .iter() + .zip(fields.iter()) + .filter(|(_, f)| { + f.attrs.iter().any(|a| { + a.path + .get_ident() + .filter(|i| i.to_string().as_str() == "state") + .is_some() + }) + }) + .map(|(m, _)| m); + #[derive(Debug, Clone)] struct DepNode<'a> { state_mem: StateMember<'a>, @@ -350,7 +475,10 @@ impl<'a> StateStruct<'a> { .flat_map(|r| r.flatten().into_iter()) .collect(); - Ok(Self { state_members }) + Ok(Self { + state_members, + child_states: child_states.collect(), + }) } } } @@ -484,23 +612,23 @@ impl<'a> StateMember<'a> { DepKind::Node => { quote! { dioxus_native_core::state::NodeStatesChanged{ - node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], + node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*], } } } DepKind::Child => { quote! { dioxus_native_core::state::ChildStatesChanged{ - node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], - child_dep: &[#(dioxus_native_core::state::MemberId(#child_dep), )*], + node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*], + child_dep: vec![#(dioxus_native_core::state::MemberId(#child_dep), )*], } } } DepKind::Parent => { quote! { dioxus_native_core::state::ParentStatesChanged{ - node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], - parent_dep: &[#(dioxus_native_core::state::MemberId(#parent_dep), )*], + node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*], + parent_dep: vec![#(dioxus_native_core::state::MemberId(#parent_dep), )*], } } } @@ -515,7 +643,6 @@ impl<'a> StateMember<'a> { match self.dep_kind { DepKind::Node => { quote!({ - // println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){ Some(#states_changed) } else{ @@ -525,7 +652,6 @@ impl<'a> StateMember<'a> { } DepKind::Child => { 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{ @@ -535,7 +661,6 @@ impl<'a> StateMember<'a> { } DepKind::Parent => { 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{ @@ -548,7 +673,6 @@ impl<'a> StateMember<'a> { match self.dep_kind { DepKind::Node => { quote!({ - // println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, &(), #get_ctx){ Some(#states_changed) } else{ @@ -558,7 +682,6 @@ impl<'a> StateMember<'a> { } DepKind::Child => { quote!({ - // println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){ Some(#states_changed) } else{ @@ -568,7 +691,6 @@ impl<'a> StateMember<'a> { } DepKind::Parent => { quote!({ - println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, Some(&()), #get_ctx){ Some(#states_changed) } else{ diff --git a/packages/native-core-macro/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs index 611fa752e..dd0e09f73 100644 --- a/packages/native-core-macro/tests/update_state.rs +++ b/packages/native-core-macro/tests/update_state.rs @@ -8,12 +8,36 @@ use dioxus_native_core::real_dom::*; use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; use dioxus_native_core_macro::State; +#[derive(Debug, Clone, Default, State)] +struct CallCounterStatePart1 { + #[child_dep_state(child_counter)] + child_counter: ChildDepCallCounter, +} + +#[derive(Debug, Clone, Default, State)] +struct CallCounterStatePart2 { + #[parent_dep_state(parent_counter)] + parent_counter: ParentDepCallCounter, +} + +#[derive(Debug, Clone, Default, State)] +struct CallCounterStatePart3 { + #[node_dep_state()] + node_counter: NodeDepCallCounter, +} + #[derive(Debug, Clone, Default, State)] struct CallCounterState { #[child_dep_state(child_counter)] child_counter: ChildDepCallCounter, + #[state] + part2: CallCounterStatePart2, #[parent_dep_state(parent_counter)] parent_counter: ParentDepCallCounter, + #[state] + part1: CallCounterStatePart1, + #[state] + part3: CallCounterStatePart3, #[node_dep_state()] node_counter: NodeDepCallCounter, } @@ -261,8 +285,11 @@ fn state_reduce_initally_called_minimally() { let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { + assert_eq!(n.state.part1.child_counter.0, 1); assert_eq!(n.state.child_counter.0, 1); + assert_eq!(n.state.part2.parent_counter.0, 1); assert_eq!(n.state.parent_counter.0, 1); + assert_eq!(n.state.part3.node_counter.0, 1); assert_eq!(n.state.node_counter.0, 1); }); } @@ -329,6 +356,7 @@ fn state_reduce_parent_called_minimally_on_update() { let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { + assert_eq!(n.state.part2.parent_counter.0, 2); assert_eq!(n.state.parent_counter.0, 2); }); } @@ -398,6 +426,10 @@ fn state_reduce_child_called_minimally_on_update() { dom.traverse_depth_first(|n| { println!("{:?}", n); + assert_eq!( + n.state.part1.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 }); }); } @@ -487,3 +519,15 @@ fn dependancies_order_independant() { assert_eq!(&n.state.c, &c); }); } + +#[derive(Clone, Default, State)] +struct DependanciesStateTest { + #[node_dep_state(c)] + b: BDepCallCounter, + #[node_dep_state()] + c: CDepCallCounter, + #[node_dep_state(b)] + a: ADepCallCounter, + #[state] + child: UnorderedDependanciesState, +} diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 7bfb27492..b43327713 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -308,7 +308,7 @@ impl RealDom { 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 { + for m in &members_effected.node_dep { if let Err(idx) = ids.binary_search(m) { ids.insert(idx, *m); } @@ -348,12 +348,12 @@ impl RealDom { { 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); + if let Err(idx) = ids.binary_search(&m) { + ids.insert(idx, m); } } for m in members_effected.child_dep { - changed.push(*m); + changed.push(m); } } i += 1; @@ -416,12 +416,12 @@ impl RealDom { { 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); + if let Err(idx) = ids.binary_search(&m) { + ids.insert(idx, m); } } for m in members_effected.parent_dep { - changed.push(*m); + changed.push(m); } } i += 1; diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 9e008d416..b7c1f59c7 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,4 +1,8 @@ -use std::{cmp::Ordering, fmt::Debug}; +use std::{ + cmp::Ordering, + fmt::Debug, + ops::{Add, AddAssign, Sub, SubAssign}, +}; use anymap::AnyMap; use dioxus_core::VNode; @@ -81,22 +85,24 @@ pub trait NodeDepState { #[derive(Debug)] pub struct ChildStatesChanged { - pub node_dep: &'static [MemberId], - pub child_dep: &'static [MemberId], + pub node_dep: Vec, + pub child_dep: Vec, } #[derive(Debug)] pub struct ParentStatesChanged { - pub node_dep: &'static [MemberId], - pub parent_dep: &'static [MemberId], + pub node_dep: Vec, + pub parent_dep: Vec, } #[derive(Debug)] pub struct NodeStatesChanged { - pub node_dep: &'static [MemberId], + pub node_dep: Vec, } pub trait State: Default + Clone { + const SIZE: usize; + fn update_node_dep_state<'a>( &'a mut self, ty: MemberId, @@ -123,7 +129,7 @@ pub trait State: Default + Clone { ty: MemberId, node: &'a VNode<'a>, vdom: &'a dioxus_core::VirtualDom, - children: &[&Self], + children: &Vec<&Self>, ctx: &AnyMap, ) -> Option; /// This must be a valid resolution order. (no nodes updated before a state they rely on) @@ -165,3 +171,29 @@ impl NodeDepState for () { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct MemberId(pub usize); + +impl Sub for MemberId { + type Output = MemberId; + fn sub(self, rhs: usize) -> Self::Output { + MemberId(self.0 - rhs) + } +} + +impl Add for MemberId { + type Output = MemberId; + fn add(self, rhs: usize) -> Self::Output { + MemberId(self.0 + rhs) + } +} + +impl SubAssign for MemberId { + fn sub_assign(&mut self, rhs: usize) { + *self = *self - rhs; + } +} + +impl AddAssign for MemberId { + fn add_assign(&mut self, rhs: usize) { + *self = *self + rhs; + } +}