From bf20b66b382d80c921f94e60d870388d473f29ee Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 3 Jan 2023 12:35:48 -0600 Subject: [PATCH] make the real dom generic over custom types --- packages/native-core-macro/src/lib.rs | 64 +++++++++++++++---- packages/native-core/src/node.rs | 6 +- packages/native-core/src/real_dom.rs | 41 ++++++------ packages/native-core/src/state.rs | 8 +-- .../src/utils/persistant_iterator.rs | 24 +++++-- 5 files changed, 99 insertions(+), 44 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 75b814347..29ee4b055 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -124,6 +124,17 @@ enum DependencyKind { /// # The state attribute /// The state macro declares a member that implements the State trait. This allows you to organize your state into multiple isolated components. /// Unlike the other attributes, the state attribute does not accept any arguments, because a nested state cannot depend on any other part of the state. +/// +/// # Custom values +/// +/// If your state has a custom value type you can specify it with the state attribute. +/// +/// ```rust, ignore +/// #[derive(State)] +/// #[state(custom_value = MyCustomType)] +/// struct MyStruct { +/// // ... +/// } #[proc_macro_derive( State, attributes(node_dep_state, child_dep_state, parent_dep_state, state) @@ -134,6 +145,33 @@ pub fn state_macro_derive(input: TokenStream) -> TokenStream { } fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { + let custom_type = ast + .attrs + .iter() + .find(|a| a.path.is_ident("state")) + .and_then(|attr| { + // parse custom_type = "MyType" + let assignment = attr.parse_args::().unwrap(); + if let syn::Expr::Assign(assign) = assignment { + let (left, right) = (&*assign.left, &*assign.right); + if let syn::Expr::Path(e) = left { + let path = &e.path; + if let Some(ident) = path.get_ident() { + if ident == "custom_value" { + return match right { + syn::Expr::Path(e) => { + let path = &e.path; + Some(quote! {#path}) + } + _ => None, + }; + } + } + } + } + None + }) + .unwrap_or(quote! {()}); let type_name = &ast.ident; let fields: Vec<_> = match &ast.data { syn::Data::Struct(data) => match &data.fields { @@ -162,12 +200,12 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let impl_members = state_strct .state_members .iter() - .map(|m| m.impl_pass(state_strct.ty)); + .map(|m| m.impl_pass(state_strct.ty, &custom_type)); let gen = quote! { #(#impl_members)* - impl State for #type_name { - const PASSES: &'static [dioxus_native_core::AnyPass>] = &[ + impl State<#custom_type> for #type_name { + const PASSES: &'static [dioxus_native_core::AnyPass>] = &[ #(#passes),* ]; const MASKS: &'static [dioxus_native_core::NodeMask] = &[#(#member_types::NODE_MASK),*]; @@ -378,7 +416,11 @@ impl<'a> StateMember<'a> { } /// generate code to call the resolve function for the state. This does not handle checking if resolving the state is necessary, or marking the states that depend on this state as dirty. - fn impl_pass(&self, parent_type: &Ident) -> quote::__private::TokenStream { + fn impl_pass( + &self, + parent_type: &Ident, + custom_type: impl ToTokens, + ) -> quote::__private::TokenStream { let ident = &self.mem.ident; let get_ctx = if let Some(ctx_ty) = &self.ctx_ty { if ctx_ty == &parse_quote!(()) { @@ -399,8 +441,8 @@ impl<'a> StateMember<'a> { let impl_specific = match self.dep_kind { DependencyKind::Node => { quote! { - impl dioxus_native_core::NodePass> for #unit_type { - fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type>, ctx: &dioxus_native_core::SendAnyMap) -> bool { + impl dioxus_native_core::NodePass> for #unit_type { + fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type, #custom_type>, ctx: &dioxus_native_core::SendAnyMap) -> bool { node.state.#ident.reduce(#node_view, (#(&node.state.#dep_idents,)*), #get_ctx) } } @@ -437,11 +479,11 @@ impl<'a> StateMember<'a> { } }; quote!( - impl dioxus_native_core::UpwardPass> for #unit_type{ + impl dioxus_native_core::UpwardPass> for #unit_type{ fn pass<'a>( &self, - node: &mut dioxus_native_core::node::Node<#parent_type>, - children: &mut dyn Iterator>, + node: &mut dioxus_native_core::node::Node<#parent_type, #custom_type>, + children: &mut dyn Iterator>, ctx: &dioxus_native_core::SendAnyMap, ) -> dioxus_native_core::PassReturn { let update = node.state.#ident.reduce(#node_view, children.map(|c| (#(&c.state.#dep_idents,)*)), #get_ctx); @@ -481,8 +523,8 @@ impl<'a> StateMember<'a> { } }; quote!( - impl dioxus_native_core::DownwardPass> for #unit_type { - fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type>, parent: Option<&mut dioxus_native_core::node::Node<#parent_type>>, ctx: &dioxus_native_core::SendAnyMap) -> dioxus_native_core::PassReturn{ + impl dioxus_native_core::DownwardPass> for #unit_type { + fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type, #custom_type>, parent: Option<&mut dioxus_native_core::node::Node<#parent_type, #custom_type>>, ctx: &dioxus_native_core::SendAnyMap) -> dioxus_native_core::PassReturn{ let update = node.state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.state.#dep_idents,)*)), #get_ctx); #update } diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index f5e93e2f5..3fe8476ea 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -5,7 +5,7 @@ use std::fmt::Debug; /// The node is stored client side and stores only basic data about the node. #[derive(Debug, Clone)] -pub struct Node { +pub struct Node, V: FromAnyValue + 'static = ()> { /// The transformed state of the node. pub state: S, /// The raw data for the node @@ -37,7 +37,7 @@ pub enum NodeType { Placeholder, } -impl Node { +impl, V: FromAnyValue> Node { pub(crate) fn new(node_type: NodeType) -> Self { Node { state: S::default(), @@ -83,7 +83,7 @@ pub enum OwnedAttributeValue { None, } -pub trait FromAnyValue { +pub trait FromAnyValue: Clone { fn from_any_value(value: &dyn AnyValue) -> Self; } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 4ef4d89c0..7ea253399 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -3,7 +3,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::fmt::Debug; use std::ops::{Deref, DerefMut, Index, IndexMut}; -use crate::node::{Node, NodeType, OwnedAttributeDiscription, OwnedAttributeValue}; +use crate::node::{FromAnyValue, Node, NodeType, OwnedAttributeDiscription, OwnedAttributeValue}; use crate::node_ref::{AttributeMask, NodeMask}; use crate::passes::DirtyNodeStates; use crate::state::State; @@ -25,9 +25,12 @@ fn mark_dirty( /// 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 [crate::state::ParentDepState], [crate::state::NodeDepState], or [crate::state::ChildDepState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes. +/// +/// # Custom values +/// To allow custom values to be passed into attributes implement FromAnyValue on a type that can represent your custom value and specify the V generic to be that type. If you have many different custom values, it can be useful to use a enum type to represent the varients. #[derive(Debug)] -pub struct RealDom { - pub tree: Tree>, +pub struct RealDom, V: FromAnyValue + 'static = ()> { + pub tree: Tree>, /// a map from element id to real node id node_id_mapping: Vec>, nodes_listening: FxHashMap>, @@ -36,14 +39,14 @@ pub struct RealDom { root_initialized: bool, } -impl Default for RealDom { +impl, V: FromAnyValue> Default for RealDom { fn default() -> Self { Self::new() } } -impl RealDom { - pub fn new() -> RealDom { +impl, V: FromAnyValue> RealDom { + pub fn new() -> RealDom { let mut root = Node::new(NodeType::Element { tag: "Root".to_string(), namespace: Some("Root".to_string()), @@ -91,7 +94,7 @@ impl RealDom { current } - fn create_node(&mut self, node: Node) -> RealNodeId { + fn create_node(&mut self, node: Node) -> RealNodeId { let node_id = self.tree.create_node(node); let node = self.tree.get_mut(node_id).unwrap(); node.node_data.node_id = node_id; @@ -349,7 +352,7 @@ impl RealDom { /// Find all nodes that are listening for an event, sorted by there height in the dom progressing starting at the bottom and progressing up. /// This can be useful to avoid creating duplicate events. - 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| { @@ -396,9 +399,9 @@ impl RealDom { } } -impl RealDom +impl + Sync, V: FromAnyValue> RealDom where - Tree>: Sync + Send, + Tree>: Sync + Send, { /// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts. /// This will resolve the state in parallel @@ -411,43 +414,43 @@ where } } -impl Deref for RealDom { - type Target = Tree>; +impl, V: FromAnyValue> Deref for RealDom { + type Target = Tree>; fn deref(&self) -> &Self::Target { &self.tree } } -impl DerefMut for RealDom { +impl, V: FromAnyValue> DerefMut for RealDom { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.tree } } -impl Index for RealDom { - type Output = Node; +impl, V: FromAnyValue> Index for RealDom { + type Output = Node; fn index(&self, id: ElementId) -> &Self::Output { self.tree.get(self.element_to_node_id(id)).unwrap() } } -impl Index for RealDom { - type Output = Node; +impl, V: FromAnyValue> Index for RealDom { + type Output = Node; fn index(&self, idx: RealNodeId) -> &Self::Output { self.tree.get(idx).unwrap() } } -impl IndexMut for RealDom { +impl, V: FromAnyValue> IndexMut for RealDom { fn index_mut(&mut self, id: ElementId) -> &mut Self::Output { self.tree.get_mut(self.element_to_node_id(id)).unwrap() } } -impl IndexMut for RealDom { +impl, V: FromAnyValue> IndexMut for RealDom { fn index_mut(&mut self, idx: RealNodeId) -> &mut Self::Output { self.tree.get_mut(idx).unwrap() } diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 2e8eed085..44eb81e97 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -210,14 +210,14 @@ pub trait NodeDepState { } /// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom]. -pub trait State: Default + Clone + 'static { +pub trait State: Default + Clone + 'static { #[doc(hidden)] - const PASSES: &'static [AnyPass>]; + const PASSES: &'static [AnyPass>]; #[doc(hidden)] const MASKS: &'static [NodeMask]; #[doc(hidden)] - fn update> + Sync + Send>( + fn update> + Sync + Send>( dirty: DirtyNodeStates, tree: &mut T, ctx: SendAnyMap, @@ -227,7 +227,7 @@ pub trait State: Default + Clone + 'static { } #[doc(hidden)] - fn update_single_threaded>>( + fn update_single_threaded>>( dirty: DirtyNodeStates, tree: &mut T, ctx: SendAnyMap, diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 7a34b90ad..521d449fc 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -1,4 +1,10 @@ -use crate::{node::NodeType, real_dom::RealDom, state::State, tree::TreeView, NodeId, RealNodeId}; +use crate::{ + node::{FromAnyValue, NodeType}, + real_dom::RealDom, + state::State, + tree::TreeView, + NodeId, RealNodeId, +}; use dioxus_core::{Mutation, Mutations}; use std::fmt::Debug; @@ -67,7 +73,11 @@ impl PersistantElementIter { /// remove stale element refreneces /// returns true if the focused element is removed - pub fn prune(&mut self, mutations: &Mutations, rdom: &RealDom) -> bool { + pub fn prune, V: FromAnyValue>( + &mut self, + mutations: &Mutations, + rdom: &RealDom, + ) -> bool { let mut changed = false; let ids_removed: Vec<_> = mutations .edits @@ -124,7 +134,7 @@ impl PersistantElementIter { } /// get the next element - pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { + pub fn next, V: FromAnyValue>(&mut self, rdom: &RealDom) -> ElementProduced { if self.stack.is_empty() { let id = NodeId(0); let new = (id, NodePosition::AtNode); @@ -160,12 +170,12 @@ impl PersistantElementIter { } /// get the previous element - pub fn prev(&mut self, rdom: &RealDom) -> ElementProduced { + pub fn prev, V: FromAnyValue>(&mut self, rdom: &RealDom) -> ElementProduced { // recursively add the last child element to the stack - fn push_back( + fn push_back, V: FromAnyValue>( stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>, new_node: RealNodeId, - rdom: &RealDom, + rdom: &RealDom, ) -> RealNodeId { match &rdom[new_node].node_data.node_type { NodeType::Element { .. } => { @@ -228,7 +238,7 @@ impl PersistantElementIter { #[derive(Default, Clone, Debug)] struct Empty {} -impl State for Empty { +impl State<()> for Empty { const PASSES: &'static [crate::AnyPass>] = &[]; const MASKS: &'static [crate::NodeMask] = &[];