wip: more work on diffing machine
This commit is contained in:
1 parent
ca3b80f069
commit
9813f23cdf
9 files changed
+172
-299
No files matched your search
+1
-1
@@ -55,7 +55,7 @@ members = [
|
|||||||
"packages/core-macro",
|
"packages/core-macro",
|
||||||
"packages/core",
|
"packages/core",
|
||||||
"packages/html-namespace",
|
"packages/html-namespace",
|
||||||
# "packages/web",
|
"packages/web",
|
||||||
# "packages/cli",
|
# "packages/cli",
|
||||||
# "packages/atoms",
|
# "packages/atoms",
|
||||||
# "packages/ssr",
|
# "packages/ssr",
|
||||||
|
|||||||
+112
-94
@@ -4,15 +4,15 @@
|
|||||||
//! Notice:
|
//! Notice:
|
||||||
//! ------
|
//! ------
|
||||||
//!
|
//!
|
||||||
//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and modified to support Components,
|
//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
|
||||||
//! Fragments, Suspense, and additional batching operations.
|
//! Components, Fragments, Suspense, and additional batching operations.
|
||||||
//!
|
//!
|
||||||
//! Implementation Details:
|
//! Implementation Details:
|
||||||
//! -----------------------
|
//! -----------------------
|
||||||
//!
|
//!
|
||||||
//! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
|
//! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
|
||||||
//! We don't necessarily intend for changes to happen exactly during the diffing process, so the implementor may choose
|
//! We don't necessarily require that DOM changes happen instnatly during the diffing process, so the implementor may choose
|
||||||
//! to batch nodes if it is more performant for their application. The u32 should be a no-op to hash,
|
//! to batch nodes if it is more performant for their application. We care about an ID size of u32
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
//! Further Reading and Thoughts
|
//! Further Reading and Thoughts
|
||||||
@@ -58,6 +58,8 @@ pub trait RealDom {
|
|||||||
fn create_text_node(&mut self, text: &str) -> RealDomNode;
|
fn create_text_node(&mut self, text: &str) -> RealDomNode;
|
||||||
fn create_element(&mut self, tag: &str) -> RealDomNode;
|
fn create_element(&mut self, tag: &str) -> RealDomNode;
|
||||||
fn create_element_ns(&mut self, tag: &str, namespace: &str) -> RealDomNode;
|
fn create_element_ns(&mut self, tag: &str, namespace: &str) -> RealDomNode;
|
||||||
|
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
|
||||||
|
fn create_placeholder(&mut self) -> RealDomNode;
|
||||||
|
|
||||||
// events
|
// events
|
||||||
fn new_event_listener(
|
fn new_event_listener(
|
||||||
@@ -152,7 +154,6 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
|||||||
}
|
}
|
||||||
// New node is a text element, need to replace the element with a simple text node
|
// New node is a text element, need to replace the element with a simple text node
|
||||||
VNode::Text(_) => {
|
VNode::Text(_) => {
|
||||||
log::debug!("Replacing el with text");
|
|
||||||
self.create(new_node);
|
self.create(new_node);
|
||||||
self.dom.replace_with();
|
self.dom.replace_with();
|
||||||
}
|
}
|
||||||
@@ -167,10 +168,22 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
|||||||
// New node is actually a sequence of nodes.
|
// New node is actually a sequence of nodes.
|
||||||
// We need to replace this one node with a sequence of nodes
|
// We need to replace this one node with a sequence of nodes
|
||||||
// Not yet implement because it's kinda hairy
|
// Not yet implement because it's kinda hairy
|
||||||
VNode::Fragment(_) => todo!(),
|
VNode::Fragment(new) => {
|
||||||
|
match new.children.len() {
|
||||||
|
0 => {
|
||||||
|
// remove
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
// replace
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// remove and mount many
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// New Node is actually suspended. Todo
|
// New Node is actually suspended. Todo
|
||||||
VNode::Suspended => todo!(),
|
VNode::Suspended { real } => todo!(),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Old element was text
|
// Old element was text
|
||||||
@@ -200,7 +213,7 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VNode::Suspended => todo!(),
|
VNode::Suspended { real } => todo!(),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Old element was a component
|
// Old element was a component
|
||||||
@@ -214,75 +227,74 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
|||||||
|
|
||||||
// It's also a component
|
// It's also a component
|
||||||
VNode::Component(new) => {
|
VNode::Component(new) => {
|
||||||
match old.user_fc == new.user_fc {
|
if old.user_fc == new.user_fc {
|
||||||
// Make sure we're dealing with the same component (by function pointer)
|
// Make sure we're dealing with the same component (by function pointer)
|
||||||
true => {
|
|
||||||
// Make sure the new component vnode is referencing the right scope id
|
|
||||||
let scope_id = old.ass_scope.borrow().clone();
|
|
||||||
*new.ass_scope.borrow_mut() = scope_id;
|
|
||||||
|
|
||||||
// make sure the component's caller function is up to date
|
// Make sure the new component vnode is referencing the right scope id
|
||||||
|
let scope_id = old.ass_scope.borrow().clone();
|
||||||
|
*new.ass_scope.borrow_mut() = scope_id;
|
||||||
|
|
||||||
|
// make sure the component's caller function is up to date
|
||||||
|
self.components
|
||||||
|
.with_scope(scope_id.unwrap(), |scope| {
|
||||||
|
scope.caller = Rc::downgrade(&new.caller)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// React doesn't automatically memoize, but we do.
|
||||||
|
// The cost is low enough to make it worth checking
|
||||||
|
let should_render = match old.comparator {
|
||||||
|
Some(comparator) => comparator(new),
|
||||||
|
None => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_render {
|
||||||
|
// // self.dom.commit_traversal();
|
||||||
self.components
|
self.components
|
||||||
.with_scope(scope_id.unwrap(), |scope| {
|
.with_scope(scope_id.unwrap(), |f| {
|
||||||
scope.caller = Rc::downgrade(&new.caller)
|
f.run_scope().unwrap();
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
// diff_machine.change_list.load_known_root(root_id);
|
||||||
// React doesn't automatically memoize, but we do.
|
// run the scope
|
||||||
// The cost is low enough to make it worth checking
|
//
|
||||||
let should_render = match old.comparator {
|
} else {
|
||||||
Some(comparator) => comparator(new),
|
// Component has memoized itself and doesn't need to be re-rendered.
|
||||||
None => true,
|
// We still need to make sure the child's props are up-to-date.
|
||||||
};
|
// Don't commit traversal
|
||||||
|
|
||||||
if should_render {
|
|
||||||
// // self.dom.commit_traversal();
|
|
||||||
self.components
|
|
||||||
.with_scope(scope_id.unwrap(), |f| {
|
|
||||||
f.run_scope().unwrap();
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
// diff_machine.change_list.load_known_root(root_id);
|
|
||||||
// run the scope
|
|
||||||
//
|
|
||||||
} else {
|
|
||||||
// Component has memoized itself and doesn't need to be re-rendered.
|
|
||||||
// We still need to make sure the child's props are up-to-date.
|
|
||||||
// Don't commit traversal
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// It's an entirely different component
|
// It's an entirely different component
|
||||||
false => {
|
|
||||||
// A new component has shown up! We need to destroy the old node
|
|
||||||
|
|
||||||
// Wipe the old one and plant the new one
|
// A new component has shown up! We need to destroy the old node
|
||||||
// self.dom.commit_traversal();
|
|
||||||
// self.dom.replace_node_with(old.dom_id, new.dom_id);
|
|
||||||
// self.create(new_node);
|
|
||||||
// self.dom.replace_with();
|
|
||||||
self.create(new_node);
|
|
||||||
// self.create_and_repalce(new_node, old.mounted_root.get());
|
|
||||||
|
|
||||||
// Now we need to remove the old scope and all of its descendents
|
// Wipe the old one and plant the new one
|
||||||
let old_scope = old.ass_scope.borrow().as_ref().unwrap().clone();
|
// self.dom.commit_traversal();
|
||||||
self.destroy_scopes(old_scope);
|
// self.dom.replace_node_with(old.dom_id, new.dom_id);
|
||||||
}
|
// self.create(new_node);
|
||||||
|
// self.dom.replace_with();
|
||||||
|
self.create(new_node);
|
||||||
|
// self.create_and_repalce(new_node, old.mounted_root.get());
|
||||||
|
|
||||||
|
// Now we need to remove the old scope and all of its descendents
|
||||||
|
let old_scope = old.ass_scope.borrow().as_ref().unwrap().clone();
|
||||||
|
self.destroy_scopes(old_scope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VNode::Fragment(_) => todo!(),
|
VNode::Fragment(_) => todo!(),
|
||||||
VNode::Suspended => todo!(),
|
VNode::Suspended { real } => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VNode::Fragment(old) => {
|
VNode::Fragment(old) => {
|
||||||
//
|
//
|
||||||
match new_node {
|
match new_node {
|
||||||
VNode::Fragment(_) => todo!(),
|
VNode::Fragment(new) => todo!(),
|
||||||
|
|
||||||
// going from fragment to element means we're going from many (or potentially none) to one
|
// going from fragment to element means we're going from many (or potentially none) to one
|
||||||
VNode::Element(new) => {}
|
VNode::Element(new) => {}
|
||||||
VNode::Text(_) => todo!(),
|
VNode::Text(_) => todo!(),
|
||||||
VNode::Suspended => todo!(),
|
VNode::Suspended { real } => todo!(),
|
||||||
VNode::Component(_) => todo!(),
|
VNode::Component(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,10 +302,12 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
|||||||
// a suspended node will perform a mem-copy of the previous elements until it is ready
|
// a suspended node will perform a mem-copy of the previous elements until it is ready
|
||||||
// this means that event listeners will need to be disabled and removed
|
// this means that event listeners will need to be disabled and removed
|
||||||
// it also means that props will need to disabled - IE if the node "came out of hibernation" any props should be considered outdated
|
// it also means that props will need to disabled - IE if the node "came out of hibernation" any props should be considered outdated
|
||||||
VNode::Suspended => {
|
VNode::Suspended { real: old_real } => {
|
||||||
//
|
//
|
||||||
match new_node {
|
match new_node {
|
||||||
VNode::Suspended => todo!(),
|
VNode::Suspended { real: new_real } => {
|
||||||
|
//
|
||||||
|
}
|
||||||
VNode::Element(_) => todo!(),
|
VNode::Element(_) => todo!(),
|
||||||
VNode::Text(_) => todo!(),
|
VNode::Text(_) => todo!(),
|
||||||
VNode::Fragment(_) => todo!(),
|
VNode::Fragment(_) => todo!(),
|
||||||
@@ -354,6 +368,10 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
|||||||
// to emit three instructions to (1) create a text node, (2) set its
|
// to emit three instructions to (1) create a text node, (2) set its
|
||||||
// text content, and finally (3) append the text node to this
|
// text content, and finally (3) append the text node to this
|
||||||
// parent.
|
// parent.
|
||||||
|
//
|
||||||
|
// Notice: this is a web-specific optimization and may be changed in the future
|
||||||
|
//
|
||||||
|
// TODO move over
|
||||||
// if children.len() == 1 {
|
// if children.len() == 1 {
|
||||||
// if let VNode::Text(text) = &children[0] {
|
// if let VNode::Text(text) = &children[0] {
|
||||||
// self.dom.set_text(text.text);
|
// self.dom.set_text(text.text);
|
||||||
@@ -373,7 +391,7 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VNode::Component(component) => {
|
VNode::Component(component) => {
|
||||||
self.dom.create_text_node("placeholder for vcomponent");
|
let real_id = self.dom.create_placeholder();
|
||||||
|
|
||||||
// let root_id = next_id();
|
// let root_id = next_id();
|
||||||
// self.dom.save_known_root(root_id);
|
// self.dom.save_known_root(root_id);
|
||||||
@@ -426,7 +444,6 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
|||||||
new_component.run_scope().unwrap();
|
new_component.run_scope().unwrap();
|
||||||
|
|
||||||
// And then run the diff algorithm
|
// And then run the diff algorithm
|
||||||
// todo!();
|
|
||||||
self.diff_node(new_component.old_frame(), new_component.next_frame());
|
self.diff_node(new_component.old_frame(), new_component.next_frame());
|
||||||
|
|
||||||
// Finally, insert this node as a seen node.
|
// Finally, insert this node as a seen node.
|
||||||
@@ -437,21 +454,17 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
|||||||
VNode::Fragment(frag) => {
|
VNode::Fragment(frag) => {
|
||||||
// create the children directly in the space
|
// create the children directly in the space
|
||||||
for child in frag.children {
|
for child in frag.children {
|
||||||
todo!()
|
self.create(child);
|
||||||
// self.create(child);
|
self.dom.append_child();
|
||||||
// self.dom.append_child();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VNode::Suspended => {
|
VNode::Suspended { real } => {
|
||||||
todo!("Creation of VNode::Suspended not yet supported")
|
let id = self.dom.create_placeholder();
|
||||||
|
real.set(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn iter_children(&self, node: &VNode<'a>) -> ChildIterator<'a> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||||
@@ -680,29 +693,29 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
|||||||
// [... parent]
|
// [... parent]
|
||||||
//
|
//
|
||||||
// Upon exiting, the change list stack is in the same state.
|
// Upon exiting, the change list stack is in the same state.
|
||||||
fn diff_keyed_children(&self, old: &[VNode<'a>], new: &[VNode<'a>]) {
|
fn diff_keyed_children(&self, old: &'a [VNode<'a>], new: &'a [VNode<'a>]) {
|
||||||
todo!();
|
// todo!();
|
||||||
// if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
// let mut keys = fxhash::FxHashSet::default();
|
let mut keys = fxhash::FxHashSet::default();
|
||||||
// let mut assert_unique_keys = |children: &[VNode]| {
|
let mut assert_unique_keys = |children: &'a [VNode<'a>]| {
|
||||||
// keys.clear();
|
keys.clear();
|
||||||
// for child in children {
|
for child in children {
|
||||||
// let key = child.key();
|
let key = child.key();
|
||||||
// debug_assert!(
|
debug_assert!(
|
||||||
// key.is_some(),
|
key.is_some(),
|
||||||
// "if any sibling is keyed, all siblings must be keyed"
|
"if any sibling is keyed, all siblings must be keyed"
|
||||||
// );
|
);
|
||||||
// keys.insert(key);
|
keys.insert(key);
|
||||||
// }
|
}
|
||||||
// debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
// children.len(),
|
children.len(),
|
||||||
// keys.len(),
|
keys.len(),
|
||||||
// "keyed siblings must each have a unique key"
|
"keyed siblings must each have a unique key"
|
||||||
// );
|
);
|
||||||
// };
|
};
|
||||||
// assert_unique_keys(old);
|
assert_unique_keys(old);
|
||||||
// assert_unique_keys(new);
|
assert_unique_keys(new);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// First up, we diff all the nodes with the same key at the beginning of the
|
// First up, we diff all the nodes with the same key at the beginning of the
|
||||||
// children.
|
// children.
|
||||||
@@ -1090,7 +1103,12 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
|||||||
// [... parent prev_child]
|
// [... parent prev_child]
|
||||||
// self.dom.go_to_sibling(i);
|
// self.dom.go_to_sibling(i);
|
||||||
// [... parent this_child]
|
// [... parent this_child]
|
||||||
self.dom.push_root(old_child.get_mounted_id().unwrap());
|
|
||||||
|
let did = old_child.get_mounted_id().unwrap();
|
||||||
|
if did.0 == 0 {
|
||||||
|
log::debug!("Root is bad {:#?}", old_child);
|
||||||
|
}
|
||||||
|
self.dom.push_root(did);
|
||||||
self.diff_node(old_child, new_child);
|
self.diff_node(old_child, new_child);
|
||||||
|
|
||||||
let old_id = old_child.get_mounted_id().unwrap();
|
let old_id = old_child.get_mounted_id().unwrap();
|
||||||
@@ -1270,7 +1288,7 @@ impl<'a> Iterator for ChildIterator<'a> {
|
|||||||
|
|
||||||
// Immediately abort suspended nodes - can't do anything with them yet
|
// Immediately abort suspended nodes - can't do anything with them yet
|
||||||
// VNode::Suspended => should_pop = true,
|
// VNode::Suspended => should_pop = true,
|
||||||
VNode::Suspended => todo!(),
|
VNode::Suspended { real } => todo!(),
|
||||||
|
|
||||||
// For components, we load their root and push them onto the stack
|
// For components, we load their root and push them onto the stack
|
||||||
VNode::Component(sc) => {
|
VNode::Component(sc) => {
|
||||||
@@ -1370,7 +1388,7 @@ mod tests {
|
|||||||
|
|
||||||
// These would represent failing cases.
|
// These would represent failing cases.
|
||||||
VNode::Fragment(_) => panic!("Found: Fragment"),
|
VNode::Fragment(_) => panic!("Found: Fragment"),
|
||||||
VNode::Suspended => panic!("Found: Suspended"),
|
VNode::Suspended { real } => panic!("Found: Suspended"),
|
||||||
VNode::Component(_) => panic!("Found: Component"),
|
VNode::Component(_) => panic!("Found: Component"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -611,14 +611,18 @@ where
|
|||||||
impl<'a> IntoVNode<'a> for () {
|
impl<'a> IntoVNode<'a> for () {
|
||||||
fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> {
|
fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> {
|
||||||
todo!();
|
todo!();
|
||||||
VNode::Suspended
|
VNode::Suspended {
|
||||||
|
real: Cell::new(RealDomNode::empty()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoVNode<'a> for Option<()> {
|
impl<'a> IntoVNode<'a> for Option<()> {
|
||||||
fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> {
|
fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> {
|
||||||
todo!();
|
todo!();
|
||||||
VNode::Suspended
|
VNode::Suspended {
|
||||||
|
real: Cell::new(RealDomNode::empty()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-167
@@ -35,7 +35,7 @@ pub enum VNode<'src> {
|
|||||||
/// A "suspended component"
|
/// A "suspended component"
|
||||||
/// This is a masqeurade over an underlying future that needs to complete
|
/// This is a masqeurade over an underlying future that needs to complete
|
||||||
/// When the future is completed, the VNode will then trigger a render
|
/// When the future is completed, the VNode will then trigger a render
|
||||||
Suspended,
|
Suspended { real: Cell<RealDomNode> },
|
||||||
|
|
||||||
/// A User-defined componen node (node type COMPONENT_NODE)
|
/// A User-defined componen node (node type COMPONENT_NODE)
|
||||||
Component(&'src VComponent<'src>),
|
Component(&'src VComponent<'src>),
|
||||||
@@ -49,7 +49,7 @@ impl<'a> Clone for VNode<'a> {
|
|||||||
VNode::Text(old) => VNode::Text(old.clone()),
|
VNode::Text(old) => VNode::Text(old.clone()),
|
||||||
VNode::Fragment(fragment) => VNode::Fragment(fragment),
|
VNode::Fragment(fragment) => VNode::Fragment(fragment),
|
||||||
VNode::Component(component) => VNode::Component(component),
|
VNode::Component(component) => VNode::Component(component),
|
||||||
VNode::Suspended => VNode::Suspended,
|
VNode::Suspended { real } => VNode::Suspended { real: real.clone() },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ impl<'old, 'new> VNode<'old> {
|
|||||||
}
|
}
|
||||||
VNode::Text(_) => todo!(),
|
VNode::Text(_) => todo!(),
|
||||||
VNode::Fragment(_) => todo!(),
|
VNode::Fragment(_) => todo!(),
|
||||||
VNode::Suspended => todo!(),
|
VNode::Suspended { real } => todo!(),
|
||||||
VNode::Component(_) => todo!(),
|
VNode::Component(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ impl<'a> VNode<'a> {
|
|||||||
VNode::Component(c) => c.key,
|
VNode::Component(c) => c.key,
|
||||||
|
|
||||||
// todo suspend should be allowed to have keys
|
// todo suspend should be allowed to have keys
|
||||||
VNode::Suspended => NodeKey::NONE,
|
VNode::Suspended { .. } => NodeKey::NONE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ impl<'a> VNode<'a> {
|
|||||||
VNode::Element(_) => true,
|
VNode::Element(_) => true,
|
||||||
VNode::Text(_) => true,
|
VNode::Text(_) => true,
|
||||||
VNode::Fragment(_) => false,
|
VNode::Fragment(_) => false,
|
||||||
VNode::Suspended => false,
|
VNode::Suspended { .. } => false,
|
||||||
VNode::Component(_) => false,
|
VNode::Component(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,7 @@ impl<'a> VNode<'a> {
|
|||||||
VNode::Element(el) => Some(el.dom_id.get()),
|
VNode::Element(el) => Some(el.dom_id.get()),
|
||||||
VNode::Text(te) => Some(te.dom_id.get()),
|
VNode::Text(te) => Some(te.dom_id.get()),
|
||||||
VNode::Fragment(_) => todo!(),
|
VNode::Fragment(_) => todo!(),
|
||||||
VNode::Suspended => todo!(),
|
VNode::Suspended { .. } => todo!(),
|
||||||
VNode::Component(_) => todo!(),
|
VNode::Component(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ impl Debug for VNode<'_> {
|
|||||||
VNode::Element(el) => write!(s, "element, {}", el.tag_name),
|
VNode::Element(el) => write!(s, "element, {}", el.tag_name),
|
||||||
VNode::Text(t) => write!(s, "text, {}", t.text),
|
VNode::Text(t) => write!(s, "text, {}", t.text),
|
||||||
VNode::Fragment(_) => write!(s, "fragment"),
|
VNode::Fragment(_) => write!(s, "fragment"),
|
||||||
VNode::Suspended => write!(s, "suspended"),
|
VNode::Suspended { .. } => write!(s, "suspended"),
|
||||||
VNode::Component(_) => write!(s, "component"),
|
VNode::Component(_) => write!(s, "component"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,163 +436,3 @@ impl<'a> VFragment<'a> {
|
|||||||
Self { key, children }
|
Self { key, children }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// This method converts a list of nested real/virtual nodes into a stream of nodes that are definitely associated
|
|
||||||
// /// with the real dom. The only types of nodes that may be returned are text, elemets, and components.
|
|
||||||
// ///
|
|
||||||
// /// Components *are* considered virtual, but this iterator can't necessarily handle them without the scope arena.
|
|
||||||
// ///
|
|
||||||
// /// Why?
|
|
||||||
// /// ---
|
|
||||||
// /// Fragments are seen as virtual nodes but are actually a list of possibly-real nodes.
|
|
||||||
// /// JS implementations normalize their node lists when fragments are present. Here, we just create a new iterator
|
|
||||||
// /// that iterates through the recursive nesting of fragments.
|
|
||||||
// ///
|
|
||||||
// /// Fragments are stupid and I wish we didn't need to support them.
|
|
||||||
// ///
|
|
||||||
// /// This iterator only supports 3 levels of nested fragments
|
|
||||||
// ///
|
|
||||||
// pub fn iterate_real_nodes<'a>(nodes: &'a [VNode<'a>]) -> RealNodeIterator<'a> {
|
|
||||||
// RealNodeIterator::new(nodes)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub struct RealNodeIterator<'a> {
|
|
||||||
// nodes: &'a [VNode<'a>],
|
|
||||||
|
|
||||||
// // this node is always a "real" node
|
|
||||||
// // the index is "what sibling # is it"
|
|
||||||
// // IE in a list of children on a fragment, the node will be a text node that's the 5th sibling
|
|
||||||
// node_stack: Vec<(&'a VNode<'a>, u32)>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl<'a> RealNodeIterator<'a> {
|
|
||||||
// // We immediately descend to the first real node we can find
|
|
||||||
// fn new(nodes: &'a [VNode<'a>]) -> Self {
|
|
||||||
// let mut node_stack = Vec::new();
|
|
||||||
// if nodes.len() > 0 {
|
|
||||||
// let mut cur_node = nodes.get(0).unwrap();
|
|
||||||
// loop {
|
|
||||||
// node_stack.push((cur_node, 0_u32));
|
|
||||||
// if !cur_node.is_real() {
|
|
||||||
// cur_node = cur_node.get_child(0).unwrap();
|
|
||||||
// } else {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Self { nodes, node_stack }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // // advances the cursor to the next element, panicing if we're on the 3rd level and still finding fragments
|
|
||||||
// // fn advance_cursor(&mut self) {
|
|
||||||
// // let (mut cur_node, mut cur_id) = self.node_stack.last().unwrap();
|
|
||||||
|
|
||||||
// // while !cur_node.is_real() {
|
|
||||||
// // match cur_node {
|
|
||||||
// // VNode::Element(_) | VNode::Text(_) => todo!(),
|
|
||||||
// // VNode::Suspended => todo!(),
|
|
||||||
// // VNode::Component(_) => todo!(),
|
|
||||||
// // VNode::Fragment(frag) => {
|
|
||||||
// // let p = frag.children;
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// fn next_node(&mut self) -> bool {
|
|
||||||
// let (mut cur_node, cur_id) = self.node_stack.last_mut().unwrap();
|
|
||||||
|
|
||||||
// match cur_node {
|
|
||||||
// VNode::Fragment(frag) => {
|
|
||||||
// //
|
|
||||||
// if *cur_id + 1 > frag.children.len() as u32 {
|
|
||||||
// self.node_stack.pop();
|
|
||||||
// let next = self.node_stack.last_mut();
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// *cur_id += 1;
|
|
||||||
// true
|
|
||||||
// }
|
|
||||||
|
|
||||||
// VNode::Element(_) => todo!(),
|
|
||||||
// VNode::Text(_) => todo!(),
|
|
||||||
// VNode::Suspended => todo!(),
|
|
||||||
// VNode::Component(_) => todo!(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn get_current_node(&self) -> Option<&VNode<'a>> {
|
|
||||||
// self.node_stack.last().map(|(node, id)| match node {
|
|
||||||
// VNode::Element(_) => todo!(),
|
|
||||||
// VNode::Text(_) => todo!(),
|
|
||||||
// VNode::Fragment(_) => todo!(),
|
|
||||||
// VNode::Suspended => todo!(),
|
|
||||||
// VNode::Component(_) => todo!(),
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl<'a> Iterator for RealNodeIterator<'a> {
|
|
||||||
// type Item = &'a VNode<'a>;
|
|
||||||
|
|
||||||
// fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
// todo!()
|
|
||||||
// // let top_idx = self.nesting_idxs.get_mut(0).unwrap();
|
|
||||||
// // let node = &self.nodes.get_mut(*top_idx as usize);
|
|
||||||
|
|
||||||
// // if node.is_none() {
|
|
||||||
// // return None;
|
|
||||||
// // }
|
|
||||||
// // let node = node.unwrap();
|
|
||||||
|
|
||||||
// // match node {
|
|
||||||
// // VNode::Element(_) | VNode::Text(_) => {
|
|
||||||
// // *top_idx += 1;
|
|
||||||
// // return Some(node);
|
|
||||||
// // }
|
|
||||||
// // VNode::Suspended => todo!(),
|
|
||||||
// // // we need access over the scope map
|
|
||||||
// // VNode::Component(_) => todo!(),
|
|
||||||
|
|
||||||
// // VNode::Fragment(frag) => {
|
|
||||||
// // let nest_idx = self.nesting_idxs.get_mut(1).unwrap();
|
|
||||||
// // let node = &frag.children.get_mut(*nest_idx as usize);
|
|
||||||
// // match node {
|
|
||||||
// // VNode::Element(_) | VNode::Text(_) => {
|
|
||||||
// // *nest_idx += 1;
|
|
||||||
// // return Some(node);
|
|
||||||
// // }
|
|
||||||
// // VNode::Fragment(_) => todo!(),
|
|
||||||
// // VNode::Suspended => todo!(),
|
|
||||||
// // VNode::Component(_) => todo!(),
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
mod tests {
|
|
||||||
use crate::debug_renderer::DebugRenderer;
|
|
||||||
use crate::nodebuilder::LazyNodes;
|
|
||||||
|
|
||||||
use crate as dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
#[test]
|
|
||||||
fn iterate_nodes() {
|
|
||||||
let rs = rsx! {
|
|
||||||
Fragment {
|
|
||||||
Fragment {
|
|
||||||
Fragment {
|
|
||||||
Fragment {
|
|
||||||
h1 {"abc1"}
|
|
||||||
}
|
|
||||||
h2 {"abc2"}
|
|
||||||
}
|
|
||||||
h3 {"abc3"}
|
|
||||||
}
|
|
||||||
h4 {"abc4"}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
|
|
||||||
pub struct DebugDom {
|
pub struct DebugDom {
|
||||||
counter: u32,
|
counter: u64,
|
||||||
}
|
}
|
||||||
impl DebugDom {
|
impl DebugDom {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@@ -33,6 +33,10 @@ impl RealDom for DebugDom {
|
|||||||
self.counter += 1;
|
self.counter += 1;
|
||||||
RealDomNode::new(self.counter)
|
RealDomNode::new(self.counter)
|
||||||
}
|
}
|
||||||
|
fn create_placeholder(&mut self) -> RealDomNode {
|
||||||
|
self.counter += 1;
|
||||||
|
RealDomNode::new(self.counter)
|
||||||
|
}
|
||||||
|
|
||||||
fn new_event_listener(
|
fn new_event_listener(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
@@ -64,14 +64,19 @@ pub struct VirtualDom {
|
|||||||
_root_prop_type: std::any::TypeId,
|
_root_prop_type: std::any::TypeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The `RealDomNode` is an ID handle that corresponds to a foreign DOM node.
|
||||||
|
///
|
||||||
|
/// "u64" was chosen for two reasons
|
||||||
|
/// - 0 cost hashing
|
||||||
|
/// - use with slotmap and other versioned slot arenas
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct RealDomNode(pub u32);
|
pub struct RealDomNode(pub u64);
|
||||||
impl RealDomNode {
|
impl RealDomNode {
|
||||||
pub fn new(id: u32) -> Self {
|
pub fn new(id: u64) -> Self {
|
||||||
Self(id)
|
Self(id)
|
||||||
}
|
}
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self(u32::MIN)
|
Self(u64::MIN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -795,7 +800,7 @@ Any function prefixed with "use" should not be called conditionally.
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// There are hooks going on here!
|
/// There are hooks going on here!
|
||||||
fn use_context<T: 'static>(&self) -> &'src T {
|
fn use_context<T: 'static>(&self) -> &'src Rc<T> {
|
||||||
self.try_use_context().unwrap()
|
self.try_use_context().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -871,7 +876,9 @@ Any function prefixed with "use" should not be called conditionally.
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// we need to register this task
|
// we need to register this task
|
||||||
VNode::Suspended
|
VNode::Suspended {
|
||||||
|
real: Cell::new(RealDomNode::empty()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ atoms = { path="../atoms" }
|
|||||||
async-channel = "1.6.1"
|
async-channel = "1.6.1"
|
||||||
nohash-hasher = "0.2.0"
|
nohash-hasher = "0.2.0"
|
||||||
anyhow = "1.0.41"
|
anyhow = "1.0.41"
|
||||||
|
slotmap = "1.0.3"
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3.50"
|
version = "0.3.50"
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ static Example: FC<()> = |cx| {
|
|||||||
"Jack!"
|
"Jack!"
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
|
class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
|
||||||
onclick={move |_| set_name("jill")}>
|
onclick={move |_| set_name("jill")}>
|
||||||
"Jill!"
|
"Jill!"
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
+23
-24
@@ -7,6 +7,7 @@ use dioxus_core::{
|
|||||||
};
|
};
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use nohash_hasher::IntMap;
|
use nohash_hasher::IntMap;
|
||||||
|
use slotmap::{DefaultKey, Key, KeyData};
|
||||||
use wasm_bindgen::{closure::Closure, JsCast};
|
use wasm_bindgen::{closure::Closure, JsCast};
|
||||||
use web_sys::{
|
use web_sys::{
|
||||||
window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
|
window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
|
||||||
@@ -14,7 +15,7 @@ use web_sys::{
|
|||||||
|
|
||||||
pub struct WebsysDom {
|
pub struct WebsysDom {
|
||||||
pub stack: Stack,
|
pub stack: Stack,
|
||||||
nodes: IntMap<u32, Node>,
|
nodes: slotmap::SlotMap<DefaultKey, Node>,
|
||||||
document: Document,
|
document: Document,
|
||||||
root: Element,
|
root: Element,
|
||||||
|
|
||||||
@@ -39,8 +40,6 @@ pub struct WebsysDom {
|
|||||||
// `ptns` = Percy text node separator
|
// `ptns` = Percy text node separator
|
||||||
// TODO
|
// TODO
|
||||||
last_node_was_text: bool,
|
last_node_was_text: bool,
|
||||||
|
|
||||||
node_counter: Counter,
|
|
||||||
}
|
}
|
||||||
impl WebsysDom {
|
impl WebsysDom {
|
||||||
pub fn new(root: Element) -> Self {
|
pub fn new(root: Element) -> Self {
|
||||||
@@ -52,16 +51,18 @@ impl WebsysDom {
|
|||||||
let (sender, mut receiver) = async_channel::unbounded::<EventTrigger>();
|
let (sender, mut receiver) = async_channel::unbounded::<EventTrigger>();
|
||||||
|
|
||||||
let sender_callback = Arc::new(move |ev| {
|
let sender_callback = Arc::new(move |ev| {
|
||||||
let mut c = sender.clone();
|
let c = sender.clone();
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
c.send(ev).await.unwrap();
|
c.send(ev).await.unwrap();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut nodes =
|
let mut nodes = slotmap::SlotMap::new();
|
||||||
HashMap::with_capacity_and_hasher(1000, nohash_hasher::BuildNoHashHasher::default());
|
// HashMap::with_capacity_and_hasher(1000, nohash_hasher::BuildNoHashHasher::default());
|
||||||
|
// let mut nodes =
|
||||||
|
// HashMap::with_capacity_and_hasher(1000, nohash_hasher::BuildNoHashHasher::default());
|
||||||
|
|
||||||
nodes.insert(0_u32, root.clone().dyn_into::<Node>().unwrap());
|
let root_id = nodes.insert(root.clone().dyn_into::<Node>().unwrap());
|
||||||
Self {
|
Self {
|
||||||
stack: Stack::with_capacity(10),
|
stack: Stack::with_capacity(10),
|
||||||
nodes,
|
nodes,
|
||||||
@@ -74,7 +75,6 @@ impl WebsysDom {
|
|||||||
trigger: sender_callback,
|
trigger: sender_callback,
|
||||||
root,
|
root,
|
||||||
last_node_was_text: false,
|
last_node_was_text: false,
|
||||||
node_counter: Counter(0),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,17 +84,11 @@ impl WebsysDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Counter(u32);
|
|
||||||
impl Counter {
|
|
||||||
fn next(&mut self) -> u32 {
|
|
||||||
self.0 += 1;
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl dioxus_core::diff::RealDom for WebsysDom {
|
impl dioxus_core::diff::RealDom for WebsysDom {
|
||||||
fn push_root(&mut self, root: dioxus_core::virtual_dom::RealDomNode) {
|
fn push_root(&mut self, root: dioxus_core::virtual_dom::RealDomNode) {
|
||||||
log::debug!("Called `[`push_root] {:?}", root);
|
log::debug!("Called [push_root] {:?}", root);
|
||||||
let domnode = self.nodes.get(&root.0).expect("Failed to pop know root");
|
let key: DefaultKey = KeyData::from_ffi(root.0).into();
|
||||||
|
let domnode = self.nodes.get(key).expect("Failed to pop know root");
|
||||||
self.stack.push(domnode.clone());
|
self.stack.push(domnode.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,15 +160,19 @@ impl dioxus_core::diff::RealDom for WebsysDom {
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_placeholder(&mut self) -> RealDomNode {
|
||||||
|
self.create_element("pre")
|
||||||
|
}
|
||||||
fn create_text_node(&mut self, text: &str) -> dioxus_core::virtual_dom::RealDomNode {
|
fn create_text_node(&mut self, text: &str) -> dioxus_core::virtual_dom::RealDomNode {
|
||||||
let nid = self.node_counter.next();
|
// let nid = self.node_counter.next();
|
||||||
let textnode = self
|
let textnode = self
|
||||||
.document
|
.document
|
||||||
.create_text_node(text)
|
.create_text_node(text)
|
||||||
.dyn_into::<Node>()
|
.dyn_into::<Node>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.stack.push(textnode.clone());
|
self.stack.push(textnode.clone());
|
||||||
self.nodes.insert(nid, textnode);
|
let nid = self.nodes.insert(textnode);
|
||||||
|
let nid = nid.data().as_ffi();
|
||||||
|
|
||||||
log::debug!("Called [`create_text_node`]: {}, {}", text, nid);
|
log::debug!("Called [`create_text_node`]: {}, {}", text, nid);
|
||||||
|
|
||||||
@@ -190,8 +188,8 @@ impl dioxus_core::diff::RealDom for WebsysDom {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.stack.push(el.clone());
|
self.stack.push(el.clone());
|
||||||
let nid = self.node_counter.next();
|
// let nid = self.node_counter.?next();
|
||||||
self.nodes.insert(nid, el);
|
let nid = self.nodes.insert(el).data().as_ffi();
|
||||||
log::debug!("Called [`create_element`]: {}, {:?}", tag, nid);
|
log::debug!("Called [`create_element`]: {}, {:?}", tag, nid);
|
||||||
RealDomNode::new(nid)
|
RealDomNode::new(nid)
|
||||||
}
|
}
|
||||||
@@ -209,8 +207,9 @@ impl dioxus_core::diff::RealDom for WebsysDom {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.stack.push(el.clone());
|
self.stack.push(el.clone());
|
||||||
let nid = self.node_counter.next();
|
let nid = self.nodes.insert(el).data().as_ffi();
|
||||||
self.nodes.insert(nid, el);
|
// let nid = self.node_counter.next();
|
||||||
|
// self.nodes.insert(nid, el);
|
||||||
log::debug!("Called [`create_element_ns`]: {:}", nid);
|
log::debug!("Called [`create_element_ns`]: {:}", nid);
|
||||||
RealDomNode::new(nid)
|
RealDomNode::new(nid)
|
||||||
}
|
}
|
||||||
@@ -287,7 +286,7 @@ impl dioxus_core::diff::RealDom for WebsysDom {
|
|||||||
.context("")?;
|
.context("")?;
|
||||||
let real_id = fields
|
let real_id = fields
|
||||||
.next()
|
.next()
|
||||||
.and_then(|f| f.parse::<u32>().ok().map(RealDomNode::new))
|
.and_then(|f| f.parse::<u64>().ok().map(RealDomNode::new))
|
||||||
.context("")?;
|
.context("")?;
|
||||||
|
|
||||||
// Call the trigger
|
// Call the trigger
|
||||||
|
|||||||
Reference in new issue
Block a user