Bladeren bron

feat: wire up linked nodes

Jonathan Kelley 3 jaren geleden
bovenliggende
commit
c10c1f4

+ 1 - 0
packages/core/Cargo.toml

@@ -47,6 +47,7 @@ fern = { version = "0.6.0", features = ["colored"] }
 rand = { version = "0.8.4", features = ["small_rng"] }
 simple_logger = "1.13.0"
 dioxus-core-macro = { path = "../core-macro", version = "0.1.2" }
+dioxus-hooks = { path = "../hooks" }
 # async-std = { version = "1.9.0", features = ["attributes"] }
 # criterion = "0.3.5"
 

+ 69 - 61
packages/core/src/diff.rs

@@ -240,6 +240,8 @@ impl<'bump> DiffState<'bump> {
     pub fn diff_scope(&mut self, id: &ScopeId) {
         let (old, new) = (self.scopes.wip_head(id), self.scopes.fin_head(id));
         self.stack.push(DiffInstruction::Diff { old, new });
+        self.stack.scope_stack.push(*id);
+        self.stack.push_nodes_created(0);
         self.work(|| false);
     }
 
@@ -281,13 +283,9 @@ impl<'bump> DiffState<'bump> {
             }
 
             VNode::Linked(linked) => {
-                todo!("load linked");
-                0
-                // let num_on_stack = linked.children.iter().map(|child| {
-                //     self.push_all_nodes( child)
-                // }).sum();
-                // self.mutations.push_root(node.mounted_id());
-                // num_on_stack + 1
+                let node = unsafe { &*linked.node };
+                let node: &VNode = unsafe { std::mem::transmute(node) };
+                self.push_all_nodes(node)
             }
 
             VNode::Fragment(_) | VNode::Component(_) => {
@@ -321,16 +319,7 @@ impl<'bump> DiffState<'bump> {
             }
 
             MountType::Replace { old } => {
-                // todo: a bug here where we remove a node that is alread being replaced
-                if let Some(old_id) = old.try_mounted_id() {
-                    self.mutations.replace_with(old_id, nodes_created as u32);
-                    self.remove_nodes(Some(old), false);
-                } else {
-                    if let Some(id) = self.find_first_element_id(old) {
-                        self.mutations.replace_with(id, nodes_created as u32);
-                    }
-                    self.remove_nodes(Some(old), false);
-                }
+                self.replace_node(old, nodes_created);
             }
 
             MountType::Append => {
@@ -386,8 +375,10 @@ impl<'bump> DiffState<'bump> {
 
     fn create_anchor_node(&mut self, anchor: &'bump VAnchor, node: &'bump VNode<'bump>) {
         let real_id = self.scopes.reserve_node(node);
+
         self.mutations.create_placeholder(real_id);
         anchor.dom_id.set(Some(real_id));
+
         self.stack.add_child_count(1);
     }
 
@@ -488,11 +479,13 @@ impl<'bump> DiffState<'bump> {
     }
 
     fn create_linked_node(&mut self, link: &'bump NodeLink) {
-        if let Some(cur_scope) = self.stack.current_scope() {
-            link.scope_id.set(Some(cur_scope));
-            let node: &'bump VNode<'static> = unsafe { &*link.node };
-            self.create_node(unsafe { std::mem::transmute(node) });
+        if link.scope_id.get().is_none() {
+            if let Some(cur_scope) = self.stack.current_scope() {
+                link.scope_id.set(Some(cur_scope));
+            }
         }
+        let node: &'bump VNode<'static> = unsafe { &*link.node };
+        self.create_node(unsafe { std::mem::transmute(node) });
     }
 
     // =================================
@@ -692,9 +685,16 @@ impl<'bump> DiffState<'bump> {
     }
 
     fn diff_linked_nodes(&mut self, old: &'bump NodeLink, new: &'bump NodeLink) {
-        todo!();
-        // new.dom_id.set(old.dom_id.get());
-        // self.attach_linked_node_to_scope( new);
+        if !std::ptr::eq(old.node, new.node) {
+            // if the ptrs are the same then theyr're the same
+            let old: &VNode = unsafe { std::mem::transmute(&*old.node) };
+            let new: &VNode = unsafe { std::mem::transmute(&*new.node) };
+            self.diff_node(old, new);
+        }
+
+        if new.scope_id.get().is_none() {
+            todo!("attach the link to the scope - when children are not created");
+        }
     }
 
     // =============================================
@@ -735,7 +735,14 @@ impl<'bump> DiffState<'bump> {
                     .create_children(new, MountType::Replace { old: &old[0] });
             }
             (_, [VNode::Anchor(_)]) => {
-                self.replace_and_create_many_with_one(old, &new[0]);
+                let new: &'bump VNode<'bump> = &new[0];
+                if let Some(first_old) = old.get(0) {
+                    self.remove_nodes(&old[1..], true);
+                    self.stack
+                        .create_node(new, MountType::Replace { old: first_old });
+                } else {
+                    self.stack.create_node(new, MountType::Append {});
+                }
             }
             _ => {
                 let new_is_keyed = new[0].key().is_some();
@@ -1032,7 +1039,14 @@ impl<'bump> DiffState<'bump> {
             log::debug!("old_key_to_old_index, {:#?}", old_key_to_old_index);
             log::debug!("new_index_to_old_index, {:#?}", new_index_to_old_index);
             log::debug!("shared_keys, {:#?}", shared_keys);
-            self.replace_and_create_many_with_many(old, new);
+
+            if let Some(first_old) = old.get(0) {
+                self.remove_nodes(&old[1..], true);
+                self.stack
+                    .create_children(new, MountType::Replace { old: first_old })
+            } else {
+                self.stack.create_children(new, MountType::Append {});
+            }
             return;
         }
 
@@ -1167,11 +1181,11 @@ impl<'bump> DiffState<'bump> {
                 }
                 VNode::Component(el) => {
                     let scope_id = el.associated_scope.get().unwrap();
-                    // let scope = self.scopes.get_scope(&scope_id).unwrap();
                     search_node = Some(self.scopes.root_node(&scope_id));
                 }
                 VNode::Linked(link) => {
-                    todo!("linked")
+                    let node = unsafe { std::mem::transmute(&*link.node) };
+                    search_node = Some(node);
                 }
                 VNode::Text(t) => break t.dom_id.get(),
                 VNode::Element(t) => break t.dom_id.get(),
@@ -1181,17 +1195,27 @@ impl<'bump> DiffState<'bump> {
         }
     }
 
-    fn replace_and_create_many_with_one(
-        &mut self,
-        old: &'bump [VNode<'bump>],
-        new: &'bump VNode<'bump>,
-    ) {
-        if let Some(first_old) = old.get(0) {
-            self.remove_nodes(&old[1..], true);
-            self.stack
-                .create_node(new, MountType::Replace { old: first_old });
-        } else {
-            self.stack.create_node(new, MountType::Append {});
+    fn replace_node(&mut self, old: &'bump VNode<'bump>, nodes_created: usize) {
+        match old {
+            VNode::Text(_) | VNode::Element(_) | VNode::Anchor(_) | VNode::Suspended(_) => {
+                let id = old.try_mounted_id().expect(&format!("broke on {:?}", old));
+                self.mutations.replace_with(id, nodes_created as u32);
+            }
+
+            VNode::Fragment(f) => {
+                self.replace_node(&f.children[0], nodes_created);
+                self.remove_nodes(f.children.iter().skip(1), true);
+            }
+
+            VNode::Component(c) => {
+                let node = self.scopes.fin_head(&c.associated_scope.get().unwrap());
+                self.replace_node(node, nodes_created);
+            }
+
+            VNode::Linked(l) => {
+                let node: &'bump VNode<'bump> = unsafe { std::mem::transmute(&*l.node) };
+                self.replace_node(node, nodes_created);
+            }
         }
     }
 
@@ -1206,11 +1230,13 @@ impl<'bump> DiffState<'bump> {
         for node in nodes {
             match node {
                 VNode::Text(t) => {
-                    let id = t.dom_id.get().unwrap();
-                    self.scopes.collect_garbage(id);
+                    // this check exists because our null node will be removed but does not have an ID
+                    if let Some(id) = t.dom_id.get() {
+                        self.scopes.collect_garbage(id);
 
-                    if gen_muts {
-                        self.mutations.remove(id.as_u64());
+                        if gen_muts {
+                            self.mutations.remove(id.as_u64());
+                        }
                     }
                 }
                 VNode::Suspended(s) => {
@@ -1259,24 +1285,6 @@ impl<'bump> DiffState<'bump> {
         }
     }
 
-    /// Remove all the old nodes and replace them with newly created new nodes.
-    ///
-    /// The new nodes *will* be created - don't create them yourself!
-    fn replace_and_create_many_with_many(
-        &mut self,
-
-        old: &'bump [VNode<'bump>],
-        new: &'bump [VNode<'bump>],
-    ) {
-        if let Some(first_old) = old.get(0) {
-            self.remove_nodes(&old[1..], true);
-            self.stack
-                .create_children(new, MountType::Replace { old: first_old })
-        } else {
-            self.stack.create_children(new, MountType::Append {});
-        }
-    }
-
     /// Adds a listener closure to a scope during diff.
     fn attach_listener_to_scope(&mut self, listener: &'bump Listener<'bump>, scope: &Scope) {
         let long_listener = unsafe { std::mem::transmute(listener) };

+ 1 - 0
packages/core/src/nodes.rs

@@ -172,6 +172,7 @@ impl<'src> VNode<'src> {
             VNode::Element(el) => el.dom_id.get(),
             VNode::Anchor(el) => el.dom_id.get(),
             VNode::Suspended(el) => el.dom_id.get(),
+
             VNode::Linked(_) => None,
             VNode::Fragment(_) => None,
             VNode::Component(_) => None,

+ 67 - 66
packages/core/src/scopearena.rs

@@ -1,10 +1,9 @@
+use bumpalo::Bump;
+use futures_channel::mpsc::UnboundedSender;
 use fxhash::FxHashMap;
 use slab::Slab;
 use std::cell::{Cell, RefCell};
 
-use bumpalo::{boxed::Box as BumpBox, Bump};
-use futures_channel::mpsc::UnboundedSender;
-
 use crate::innerlude::*;
 
 pub type FcSlot = *const ();
@@ -61,71 +60,71 @@ impl ScopeArena {
         height: u32,
         subtree: u32,
     ) -> ScopeId {
-        if let Some(id) = self.free_scopes.borrow_mut().pop() {
-            // have already called drop on it - the slot is still chillin tho
-            // let scope = unsafe { &mut *self.scopes.borrow()[id.0 as usize] };
+        let scope_id = ScopeId(self.scopes.borrow().len());
+
+        let (node_capacity, hook_capacity) = {
+            let heuristics = self.heuristics.borrow();
+            if let Some(heuristic) = heuristics.get(&fc_ptr) {
+                (heuristic.node_arena_size, heuristic.hook_arena_size)
+            } else {
+                (0, 0)
+            }
+        };
+
+        let mut frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)];
+
+        frames[0].nodes.get_mut().push({
+            let vnode = frames[0]
+                .bump
+                .alloc(VNode::Text(frames[0].bump.alloc(VText {
+                    dom_id: Default::default(),
+                    is_static: false,
+                    text: "",
+                })));
+            unsafe { std::mem::transmute(vnode as *mut VNode) }
+        });
+
+        frames[1].nodes.get_mut().push({
+            let vnode = frames[1]
+                .bump
+                .alloc(VNode::Text(frames[1].bump.alloc(VText {
+                    dom_id: Default::default(),
+                    is_static: false,
+                    text: "",
+                })));
+            unsafe { std::mem::transmute(vnode as *mut VNode) }
+        });
 
-            todo!("override the scope contents");
+        let mut new_scope = Scope {
+            sender: self.sender.clone(),
+            our_arena_idx: scope_id,
+            parent_scope,
+            height,
+            frames,
+            subtree: Cell::new(subtree),
+            is_subtree_root: Cell::new(false),
+
+            caller,
+            generation: 0.into(),
+
+            hooks: HookList::new(hook_capacity),
+            shared_contexts: Default::default(),
+
+            items: RefCell::new(SelfReferentialItems {
+                listeners: Default::default(),
+                borrowed_props: Default::default(),
+                suspended_nodes: Default::default(),
+                tasks: Default::default(),
+                pending_effects: Default::default(),
+            }),
+        };
+
+        if let Some(id) = self.free_scopes.borrow_mut().pop() {
+            let scope = unsafe { self.get_scope_mut(&id) }.unwrap();
+            std::mem::swap(&mut new_scope, scope);
             id
         } else {
-            let scope_id = ScopeId(self.scopes.borrow().len());
-
-            let (node_capacity, hook_capacity) = {
-                let heuristics = self.heuristics.borrow();
-                if let Some(heuristic) = heuristics.get(&fc_ptr) {
-                    (heuristic.node_arena_size, heuristic.hook_arena_size)
-                } else {
-                    (0, 0)
-                }
-            };
-
-            let mut frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)];
-
-            frames[0].nodes.get_mut().push({
-                let vnode = frames[0]
-                    .bump
-                    .alloc(VNode::Text(frames[0].bump.alloc(VText {
-                        dom_id: Default::default(),
-                        is_static: false,
-                        text: "",
-                    })));
-                unsafe { std::mem::transmute(vnode as *mut VNode) }
-            });
-
-            frames[1].nodes.get_mut().push({
-                let vnode = frames[1]
-                    .bump
-                    .alloc(VNode::Text(frames[1].bump.alloc(VText {
-                        dom_id: Default::default(),
-                        is_static: false,
-                        text: "",
-                    })));
-                unsafe { std::mem::transmute(vnode as *mut VNode) }
-            });
-
-            let scope = self.bump.alloc(Scope {
-                sender: self.sender.clone(),
-                parent_scope,
-                our_arena_idx: scope_id,
-                height,
-                subtree: Cell::new(subtree),
-                is_subtree_root: Cell::new(false),
-                frames,
-
-                hooks: HookList::new(hook_capacity),
-                shared_contexts: Default::default(),
-                caller,
-                generation: 0.into(),
-
-                items: RefCell::new(SelfReferentialItems {
-                    listeners: Default::default(),
-                    borrowed_props: Default::default(),
-                    suspended_nodes: Default::default(),
-                    tasks: Default::default(),
-                    pending_effects: Default::default(),
-                }),
-            });
-
+            let scope = self.bump.alloc(new_scope);
             self.scopes.borrow_mut().push(scope);
             scope_id
         }
@@ -274,6 +273,8 @@ impl ScopeArena {
                 // self.
             }
 
+            // make the "wip frame" contents the "finished frame"
+            // any future dipping into completed nodes after "render" will go through "fin head"
             scope.cycle_frame();
             true
         } else {
@@ -300,6 +301,6 @@ impl ScopeArena {
     }
 
     pub fn root_node(&self, id: &ScopeId) -> &VNode {
-        self.wip_head(id)
+        self.fin_head(id)
     }
 }

+ 8 - 4
packages/core/src/virtual_dom.rs

@@ -198,15 +198,19 @@ impl VirtualDom {
         let caller_ref: *mut dyn Fn(&Scope) -> Element = Box::into_raw(caller);
         let base_scope = scopes.new_with_key(root as _, caller_ref, None, 0, 0);
 
+        let pending_messages = VecDeque::new();
+        let mut dirty_scopes = IndexSet::new();
+        dirty_scopes.insert(base_scope);
+
         Self {
             scopes: Box::new(scopes),
             base_scope,
             receiver,
             // todo: clean this up manually?
             _root_caller: caller_ref,
-            pending_messages: VecDeque::new(),
+            pending_messages,
             pending_futures: Default::default(),
-            dirty_scopes: Default::default(),
+            dirty_scopes,
             sender,
         }
     }
@@ -462,8 +466,8 @@ impl VirtualDom {
                     self.dirty_scopes.remove(&scope);
                 }
 
-                // I think the stack should be empty at the end of diffing?
-                debug_assert_eq!(stack.scope_stack.len(), 1);
+                // // I think the stack should be empty at the end of diffing?
+                // debug_assert_eq!(stack.scope_stack.len(), 1);
 
                 committed_mutations.push(mutations);
             } else {

+ 179 - 0
packages/core/tests/lifecycle.rs

@@ -3,7 +3,11 @@
 //! Tests for the lifecycle of components.
 use dioxus::prelude::*;
 use dioxus_core as dioxus;
+use dioxus_core::DomEdit::*;
+use dioxus_core::ScopeId;
+
 use dioxus_core_macro::*;
+use dioxus_hooks::*;
 use dioxus_html as dioxus_elements;
 use std::sync::{Arc, Mutex};
 
@@ -41,3 +45,178 @@ fn manual_diffing() {
 
     log::debug!("edits: {:?}", edits);
 }
+
+#[test]
+fn events_generate() {
+    static App: FC<()> = |cx, _| {
+        let mut count = use_state(cx, || 0);
+
+        let inner = match *count {
+            0 => {
+                rsx! {
+                    div {
+                        onclick: move |_| count += 1,
+                        div {
+                            "nested"
+                        }
+                        "Click me!"
+                    }
+                }
+            }
+            _ => todo!(),
+        };
+
+        cx.render(inner)
+    };
+
+    let mut dom = VirtualDom::new(App);
+    let mut channel = dom.get_scheduler_channel();
+    assert!(dom.has_any_work());
+
+    let edits = dom.work_with_deadline(|| false);
+    assert_eq!(
+        edits[0].edits,
+        [
+            CreateElement {
+                tag: "div",
+                root: 0,
+            },
+            NewEventListener {
+                event_name: "click",
+                scope: ScopeId(0),
+                root: 0,
+            },
+            CreateElement {
+                tag: "div",
+                root: 1,
+            },
+            CreateTextNode {
+                text: "nested",
+                root: 2,
+            },
+            AppendChildren { many: 1 },
+            CreateTextNode {
+                text: "Click me!",
+                root: 3,
+            },
+            AppendChildren { many: 2 },
+        ]
+    )
+}
+
+#[test]
+fn components_generate() {
+    static App: FC<()> = |cx, _| {
+        let mut render_phase = use_state(cx, || 0);
+        render_phase += 1;
+
+        cx.render(match *render_phase {
+            0 => rsx!("Text0"),
+            1 => rsx!(div {}),
+            2 => rsx!("Text2"),
+            3 => rsx!(Child {}),
+            4 => rsx!({ None as Option<()> }),
+            5 => rsx!("text 3"),
+            6 => rsx!({ (0..2).map(|f| rsx!("text {f}")) }),
+            7 => rsx!(Child {}),
+            _ => todo!(),
+        })
+    };
+
+    static Child: FC<()> = |cx, _| {
+        println!("running child");
+        cx.render(rsx! {
+            h1 {}
+        })
+    };
+
+    let mut dom = VirtualDom::new(App);
+    let edits = dom.rebuild();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateTextNode {
+                text: "Text0",
+                root: 0,
+            },
+            AppendChildren { many: 1 },
+        ]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateElement {
+                tag: "div",
+                root: 1,
+            },
+            ReplaceWith { root: 0, m: 1 },
+        ]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateTextNode {
+                text: "Text2",
+                root: 2,
+            },
+            ReplaceWith { root: 1, m: 1 },
+        ]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateElement { tag: "h1", root: 3 },
+            ReplaceWith { root: 2, m: 1 },
+        ]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [CreatePlaceholder { root: 4 }, ReplaceWith { root: 3, m: 1 },]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateTextNode {
+                text: "text 3",
+                root: 5,
+            },
+            ReplaceWith { root: 4, m: 1 },
+        ]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateTextNode {
+                text: "text 0",
+                root: 6,
+            },
+            CreateTextNode {
+                text: "text 1",
+                root: 7,
+            },
+            ReplaceWith { root: 5, m: 2 },
+        ]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateElement { tag: "h1", root: 8 },
+            ReplaceWith { root: 6, m: 1 },
+            Remove { root: 7 },
+        ]
+    );
+}