ソースを参照

fix native core dependancies in a different direction than the pass direction

Evan Almloff 2 年 前
コミット
d29514968f

+ 1 - 1
packages/native-core-macro/src/lib.rs

@@ -325,7 +325,7 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream {
 
             #(#items)*
 
-            fn workload_system(type_id: std::any::TypeId, dependants: dioxus_native_core::exports::FxHashSet<std::any::TypeId>, pass_direction: dioxus_native_core::prelude::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem {
+            fn workload_system(type_id: std::any::TypeId, dependants: std::sync::Arc<dioxus_native_core::prelude::Dependants>, pass_direction: dioxus_native_core::prelude::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem {
                 use dioxus_native_core::exports::shipyard::{IntoWorkloadSystem, Get, AddComponent};
                 use dioxus_native_core::tree::TreeRef;
                 use dioxus_native_core::prelude::{NodeType, NodeView};

+ 175 - 0
packages/native-core/examples/font_size.rs

@@ -0,0 +1,175 @@
+use dioxus_native_core::exports::shipyard::Component;
+use dioxus_native_core::node_ref::*;
+use dioxus_native_core::prelude::*;
+use dioxus_native_core::real_dom::NodeTypeMut;
+use dioxus_native_core_macro::partial_derive_state;
+
+// All states need to derive Component
+#[derive(Default, Debug, Copy, Clone, Component)]
+struct Size(f64, f64);
+
+/// Derive some of the boilerplate for the State implementation
+#[partial_derive_state]
+impl State for Size {
+    type ParentDependencies = ();
+
+    // The size of the current node depends on the size of its children
+    type ChildDependencies = (Self,);
+
+    type NodeDependencies = (FontSize,);
+
+    // Size only cares about the width, height, and text parts of the current node
+    const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
+        // Get access to the width and height attributes
+        .with_attrs(AttributeMaskBuilder::Some(&["width", "height"]))
+        // Get access to the text of the node
+        .with_text();
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        (font_size,): <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _: &SendAnyMap,
+    ) -> bool {
+        let font_size = font_size.size;
+        let mut width;
+        let mut height;
+        if let Some(text) = node_view.text() {
+            // if the node has text, use the text to size our object
+            width = text.len() as f64 * font_size;
+            height = font_size;
+        } else {
+            // otherwise, the size is the maximum size of the children
+            width = children
+                .iter()
+                .map(|(item,)| item.0)
+                .reduce(|accum, item| if accum >= item { accum } else { item })
+                .unwrap_or(0.0);
+
+            height = children
+                .iter()
+                .map(|(item,)| item.1)
+                .reduce(|accum, item| if accum >= item { accum } else { item })
+                .unwrap_or(0.0);
+        }
+        // if the node contains a width or height attribute it overrides the other size
+        for a in node_view.attributes().into_iter().flatten() {
+            match &*a.attribute.name {
+                "width" => width = a.value.as_float().unwrap(),
+                "height" => height = a.value.as_float().unwrap(),
+                // because Size only depends on the width and height, no other attributes will be passed to the member
+                _ => panic!(),
+            }
+        }
+        // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
+        let changed = (width != self.0) || (height != self.1);
+        *self = Self(width, height);
+        changed
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Component)]
+struct FontSize {
+    size: f64,
+}
+
+impl Default for FontSize {
+    fn default() -> Self {
+        Self { size: 16.0 }
+    }
+}
+
+#[partial_derive_state]
+impl State for FontSize {
+    // TextColor depends on the TextColor part of the parent
+    type ParentDependencies = (Self,);
+
+    type ChildDependencies = ();
+
+    type NodeDependencies = ();
+
+    // TextColor only cares about the color attribute of the current node
+    const NODE_MASK: NodeMaskBuilder<'static> =
+        // Get access to the color attribute
+        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["font-size"]));
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _context: &SendAnyMap,
+    ) -> bool {
+        let mut new = None;
+        for attr in node_view.attributes().into_iter().flatten() {
+            if attr.attribute.name == "font-size" {
+                new = Some(FontSize {
+                    size: attr.value.as_float().unwrap(),
+                });
+            }
+        }
+        let new = new.unwrap_or(parent.map(|(p,)| *p).unwrap_or_default());
+        // check if the member has changed
+        let changed = new != *self;
+        *self = new;
+        changed
+    }
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let mut rdom: RealDom = RealDom::new([FontSize::to_type_erased(), Size::to_type_erased()]);
+
+    let mut count = 0;
+
+    // intial render
+    let text_id = rdom.create_node(format!("Count: {count}")).id();
+    let mut root = rdom.get_mut(rdom.root_id()).unwrap();
+    // set the color to red
+    if let NodeTypeMut::Element(mut element) = root.node_type_mut() {
+        element.set_attribute(("color", "style"), "red".to_string());
+        element.set_attribute(("font-size", "style"), 1.);
+    }
+    root.add_child(text_id);
+
+    let ctx = SendAnyMap::new();
+    // update the State for nodes in the real_dom tree
+    let _to_rerender = rdom.update_state(ctx);
+
+    // we need to run the vdom in a async runtime
+    tokio::runtime::Builder::new_current_thread()
+        .enable_all()
+        .build()?
+        .block_on(async {
+            loop {
+                // update the count and font size
+                count += 1;
+                let mut text = rdom.get_mut(text_id).unwrap();
+                if let NodeTypeMut::Text(mut text) = text.node_type_mut() {
+                    *text = format!("Count: {count}");
+                }
+                if let NodeTypeMut::Element(mut element) =
+                    rdom.get_mut(rdom.root_id()).unwrap().node_type_mut()
+                {
+                    element.set_attribute(("font-size", "style"), count as f64);
+                }
+
+                let ctx = SendAnyMap::new();
+                let _to_rerender = rdom.update_state(ctx);
+
+                // render...
+                rdom.traverse_depth_first(|node| {
+                    let indent = " ".repeat(node.height() as usize);
+                    let font_size = *node.get::<FontSize>().unwrap();
+                    let size = *node.get::<Size>().unwrap();
+                    let id = node.id();
+                    println!("{indent}{id:?} {font_size:?} {size:?}");
+                });
+
+                // wait 1 second
+                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+            }
+        })
+}

+ 1 - 1
packages/native-core/src/lib.rs

@@ -35,7 +35,7 @@ pub mod prelude {
     pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView, TextNode};
     pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView};
     pub use crate::passes::{run_pass, PassDirection, RunPassView, TypeErasedState};
-    pub use crate::passes::{Dependancy, DependancyView, State};
+    pub use crate::passes::{Dependancy, DependancyView, Dependants, State};
     pub use crate::real_dom::{NodeImmutable, NodeMut, NodeRef, RealDom};
     pub use crate::NodeId;
     pub use crate::SendAnyMap;

+ 60 - 32
packages/native-core/src/passes.rs

@@ -126,7 +126,7 @@ pub trait State<V: FromAnyValue + Send + Sync = ()>: Any + Send + Sync {
     /// Create a workload system for this state
     fn workload_system(
         type_id: TypeId,
-        dependants: FxHashSet<TypeId>,
+        dependants: Arc<Dependants>,
         pass_direction: PassDirection,
     ) -> WorkloadSystem;
 
@@ -138,10 +138,16 @@ pub trait State<V: FromAnyValue + Send + Sync = ()>: Any + Send + Sync {
         let node_mask = Self::NODE_MASK.build();
         TypeErasedState {
             this_type_id: TypeId::of::<Self>(),
-            combined_dependancy_type_ids: all_dependanices::<V, Self>().iter().copied().collect(),
-            parent_dependant: !Self::ParentDependencies::type_ids().is_empty(),
-            child_dependant: !Self::ChildDependencies::type_ids().is_empty(),
-            dependants: FxHashSet::default(),
+            parent_dependancies_ids: Self::ParentDependencies::type_ids()
+                .iter()
+                .copied()
+                .collect(),
+            child_dependancies_ids: Self::ChildDependencies::type_ids()
+                .iter()
+                .copied()
+                .collect(),
+            node_dependancies_ids: Self::NodeDependencies::type_ids().iter().copied().collect(),
+            dependants: Default::default(),
             mask: node_mask,
             pass_direction: pass_direction::<V, Self>(),
             workload: Self::workload_system,
@@ -166,13 +172,6 @@ fn pass_direction<V: FromAnyValue + Send + Sync, S: State<V>>() -> PassDirection
     }
 }
 
-fn all_dependanices<V: FromAnyValue + Send + Sync, S: State<V>>() -> Box<[TypeId]> {
-    let mut dependencies = S::ParentDependencies::type_ids().to_vec();
-    dependencies.extend(S::ChildDependencies::type_ids().iter());
-    dependencies.extend(S::NodeDependencies::type_ids().iter());
-    dependencies.into_boxed_slice()
-}
-
 #[doc(hidden)]
 #[derive(Borrow, BorrowInfo)]
 pub struct RunPassView<'a, V: FromAnyValue + Send + Sync = ()> {
@@ -188,7 +187,7 @@ pub struct RunPassView<'a, V: FromAnyValue + Send + Sync = ()> {
 #[doc(hidden)]
 pub fn run_pass<V: FromAnyValue + Send + Sync>(
     type_id: TypeId,
-    dependants: FxHashSet<TypeId>,
+    dependants: Arc<Dependants>,
     pass_direction: PassDirection,
     view: RunPassView<V>,
     mut update_node: impl FnMut(NodeId, &SendAnyMap) -> bool,
@@ -206,11 +205,7 @@ pub fn run_pass<V: FromAnyValue + Send + Sync>(
             while let Some((height, id)) = dirty.pop_front(type_id) {
                 if (update_node)(id, ctx) {
                     nodes_updated.insert(id);
-                    for id in tree.children_ids(id) {
-                        for dependant in &dependants {
-                            dirty.insert(*dependant, id, height + 1);
-                        }
-                    }
+                    dependants.mark_dirty(&dirty, id, &tree, height);
                 }
             }
         }
@@ -218,11 +213,7 @@ pub fn run_pass<V: FromAnyValue + Send + Sync>(
             while let Some((height, id)) = dirty.pop_back(type_id) {
                 if (update_node)(id, ctx) {
                     nodes_updated.insert(id);
-                    if let Some(id) = tree.parent_id(id) {
-                        for dependant in &dependants {
-                            dirty.insert(*dependant, id, height - 1);
-                        }
-                    }
+                    dependants.mark_dirty(&dirty, id, &tree, height);
                 }
             }
         }
@@ -230,24 +221,53 @@ pub fn run_pass<V: FromAnyValue + Send + Sync>(
             while let Some((height, id)) = dirty.pop_back(type_id) {
                 if (update_node)(id, ctx) {
                     nodes_updated.insert(id);
-                    for dependant in &dependants {
-                        dirty.insert(*dependant, id, height);
-                    }
+                    dependants.mark_dirty(&dirty, id, &tree, height);
                 }
             }
         }
     }
 }
 
+/// The states that depend on this state
+#[derive(Default, Debug, Clone, PartialEq, Eq)]
+pub struct Dependants {
+    /// The states in the parent direction that should be invalidated when this state is invalidated
+    pub parent: Vec<TypeId>,
+    /// The states in the child direction that should be invalidated when this state is invalidated
+    pub child: Vec<TypeId>,
+    /// The states in the node direction that should be invalidated when this state is invalidated
+    pub node: Vec<TypeId>,
+}
+
+impl Dependants {
+    fn mark_dirty(&self, dirty: &DirtyNodeStates, id: NodeId, tree: &impl TreeRef, height: u16) {
+        for dependant in &self.child {
+            for id in tree.children_ids(id) {
+                dirty.insert(*dependant, id, height + 1);
+            }
+        }
+
+        for dependant in &self.parent {
+            if let Some(id) = tree.parent_id(id) {
+                dirty.insert(*dependant, id, height - 1);
+            }
+        }
+
+        for dependant in &self.node {
+            dirty.insert(*dependant, id, height);
+        }
+    }
+}
+
 /// A type erased version of [`State`] that can be added to the [`crate::prelude::RealDom`] with [`crate::prelude::RealDom::new`]
 pub struct TypeErasedState<V: FromAnyValue + Send = ()> {
     pub(crate) this_type_id: TypeId,
-    pub(crate) parent_dependant: bool,
-    pub(crate) child_dependant: bool,
-    pub(crate) combined_dependancy_type_ids: FxHashSet<TypeId>,
-    pub(crate) dependants: FxHashSet<TypeId>,
+    pub(crate) parent_dependancies_ids: FxHashSet<TypeId>,
+    pub(crate) child_dependancies_ids: FxHashSet<TypeId>,
+    pub(crate) node_dependancies_ids: FxHashSet<TypeId>,
+    pub(crate) dependants: Arc<Dependants>,
     pub(crate) mask: NodeMask,
-    pub(crate) workload: fn(TypeId, FxHashSet<TypeId>, PassDirection) -> WorkloadSystem,
+    pub(crate) workload: fn(TypeId, Arc<Dependants>, PassDirection) -> WorkloadSystem,
     pub(crate) pass_direction: PassDirection,
     phantom: PhantomData<V>,
 }
@@ -260,10 +280,18 @@ impl<V: FromAnyValue + Send> TypeErasedState<V> {
             self.pass_direction,
         )
     }
+
+    pub(crate) fn combined_dependancy_type_ids(&self) -> impl Iterator<Item = TypeId> + '_ {
+        self.parent_dependancies_ids
+            .iter()
+            .chain(self.child_dependancies_ids.iter())
+            .chain(self.node_dependancies_ids.iter())
+            .copied()
+    }
 }
 
 /// The direction that a pass should be run in
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
 pub enum PassDirection {
     /// The pass should be run from the root to the leaves
     ParentToChild,

+ 45 - 12
packages/native-core/src/real_dom.rs

@@ -15,7 +15,7 @@ use crate::node::{
 };
 use crate::node_ref::{NodeMask, NodeMaskBuilder};
 use crate::node_watcher::NodeWatcher;
-use crate::passes::{DirtyNodeStates, TypeErasedState};
+use crate::passes::{DirtyNodeStates, PassDirection, TypeErasedState};
 use crate::prelude::AttributeMaskBuilder;
 use crate::tree::{TreeMut, TreeMutView, TreeRef, TreeRefView};
 use crate::NodeId;
@@ -68,12 +68,13 @@ impl<V: FromAnyValue + Send + Sync> NodesDirty<V> {
         }
     }
 
-    /// Mark a node as added or removed from the tree
+    /// Mark a node that has had a parent changed
     fn mark_parent_added_or_removed(&mut self, node_id: NodeId) {
         let hm = self.passes_updated.entry(node_id).or_default();
         for pass in &*self.passes {
-            if pass.parent_dependant {
-                hm.insert(pass.this_type_id);
+            // If any of the states in this node depend on the parent then mark them as dirty
+            for &pass in &pass.parent_dependancies_ids {
+                hm.insert(pass);
             }
         }
     }
@@ -82,8 +83,9 @@ impl<V: FromAnyValue + Send + Sync> NodesDirty<V> {
     fn mark_child_changed(&mut self, node_id: NodeId) {
         let hm = self.passes_updated.entry(node_id).or_default();
         for pass in &*self.passes {
-            if pass.child_dependant {
-                hm.insert(pass.this_type_id);
+            // If any of the states in this node depend on the children then mark them as dirty
+            for &pass in &pass.child_dependancies_ids {
+                hm.insert(pass);
             }
         }
     }
@@ -116,16 +118,46 @@ impl<V: FromAnyValue + Send + Sync> RealDom<V> {
     pub fn new(tracked_states: impl Into<Box<[TypeErasedState<V>]>>) -> RealDom<V> {
         let mut tracked_states = tracked_states.into();
         // resolve dependants for each pass
-        for i in 1..tracked_states.len() {
+        for i in 1..=tracked_states.len() {
             let (before, after) = tracked_states.split_at_mut(i);
             let (current, before) = before.split_last_mut().unwrap();
-            for pass in before.iter_mut().chain(after.iter_mut()) {
+            for state in before.iter_mut().chain(after.iter_mut()) {
+                let dependants = Arc::get_mut(&mut state.dependants).unwrap();
+                // If this node depends on the other state as a parent, then the other state should update its children of the current type when it is invalidated
                 if current
-                    .combined_dependancy_type_ids
-                    .contains(&pass.this_type_id)
+                    .parent_dependancies_ids
+                    .contains(&state.this_type_id)
+                    && !dependants.child.contains(&current.this_type_id)
                 {
-                    pass.dependants.insert(current.this_type_id);
+                    dependants.child.push(current.this_type_id);
                 }
+                // If this node depends on the other state as a child, then the other state should update its parent of the current type when it is invalidated
+                if current.child_dependancies_ids.contains(&state.this_type_id)
+                    && !dependants.parent.contains(&current.this_type_id)
+                {
+                    dependants.parent.push(current.this_type_id);
+                }
+                // If this node depends on the other state as a sibling, then the other state should update its siblings of the current type when it is invalidated
+                if current.node_dependancies_ids.contains(&state.this_type_id)
+                    && !dependants.node.contains(&current.this_type_id)
+                {
+                    dependants.node.push(current.this_type_id);
+                }
+            }
+            // If the current state depends on itself, then it should update itself when it is invalidated
+            let dependants = Arc::get_mut(&mut current.dependants).unwrap();
+            match current.pass_direction {
+                PassDirection::ChildToParent => {
+                    if !dependants.parent.contains(&current.this_type_id) {
+                        dependants.parent.push(current.this_type_id);
+                    }
+                }
+                PassDirection::ParentToChild => {
+                    if !dependants.child.contains(&current.this_type_id) {
+                        dependants.child.push(current.this_type_id);
+                    }
+                }
+                _ => {}
             }
         }
         let workload = construct_workload(&mut tracked_states);
@@ -1011,7 +1043,8 @@ fn construct_workload<V: FromAnyValue + Send + Sync>(
     // mark any dependancies
     for i in 0..unresloved_workloads.len() {
         let (_, pass, _) = &unresloved_workloads[i];
-        for ty_id in pass.combined_dependancy_type_ids.clone() {
+        let all_dependancies: Vec<_> = pass.combined_dependancy_type_ids().collect();
+        for ty_id in all_dependancies {
             let &(dependancy_id, _, _) = unresloved_workloads
                 .iter()
                 .find(|(_, pass, _)| pass.this_type_id == ty_id)