wip: moving over instructions

This commit is contained in:
Jonathan Kelley 2021-08-20 11:39:13 -04:00
parent 7b068202ce
commit 0987760958
5 changed files with 255 additions and 485 deletions

View file

@ -1,3 +1,3 @@
{
"rust-analyzer.inlayHints.enable": true
"rust-analyzer.inlayHints.enable": false
}

View file

@ -1,9 +1,10 @@
//! This module contains the stateful PriorityFiber and all methods to diff VNodes, their properties, and their children.
//! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children.
//!
//! The [`PriorityFiber`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
//! The [`DiffMachine`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
//! of mutations for the RealDom to apply.
//!
//! ## Notice:
//!
//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
//! Components, Fragments, Suspense, SubTree memoization, incremental diffing, cancelation, NodeRefs, and additional
//! batching operations.
@ -23,12 +24,13 @@
//! and brick the user's page.
//!
//! ### Fragment Support
//!
//! --------------------
//! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments
//! can be particularly challenging when they are empty, so the placeholder node lets us "reserve" a spot for the empty
//! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty
//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the NodeFactory - it is
//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. This is
//! slightly inefficient, but represents a such an uncommon use case that it is not worth optimizing.
//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding
//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to
//! the platform.
//!
//! Other implementations either don't support fragments or use a "child + sibling" pattern to represent them. Our code is
//! vastly simpler and more performant when we can just create a placeholder element while the fragment has no children.
@ -44,7 +46,7 @@
//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so its up to the reconciler to
//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP and depends on comp-time
//! hashing of the subtree from the rsx! macro. We do a very limited form of static analysis via static string pointers as
//! a way of short-circuiting the most expensive checks.
//! a way of short-circuiting the most mem-cmp expensive checks.
//!
//! ## Bloom Filter and Heuristics
//! ------------------------------
@ -62,7 +64,7 @@
//! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes
//! for the client. As new nodes are created, old nodes will be over-written.
//!
//! Further Reading and Thoughts
//! ## Further Reading and Thoughts
//! ----------------------------
//! There are more ways of increasing diff performance here that are currently not implemented.
//! More info on how to improve this diffing algorithm:
@ -81,13 +83,16 @@ use DomEdit::*;
/// Our DiffMachine is an iterative tree differ.
///
/// It uses techniques of a register-based Turing Machines to allow pausing and restarting of the diff algorithm. This
/// It uses techniques of a stack machine to allow pausing and restarting of the diff algorithm. This
/// was origially implemented using recursive techniques, but Rust lacks the abilty to call async functions recursively,
/// meaning we could not "pause" the diffing algorithm.
/// meaning we could not "pause" the original diffing algorithm.
///
/// Instead, we use a traditional stack machine approach to diff and create new nodes. The diff algorithm periodically
/// calls "yield_now" which allows the machine to pause and return control to the caller. The caller can then wait for
/// the next period of idle time, preventing our diff algorithm from blocking the maint thread.
/// the next period of idle time, preventing our diff algorithm from blocking the main thread.
///
/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's
/// stack machines all the way down!
pub struct DiffMachine<'bump> {
vdom: &'bump SharedResources,
@ -112,7 +117,6 @@ pub enum DiffInstruction<'a> {
DiffNode {
old: &'a VNode<'a>,
new: &'a VNode<'a>,
progress: usize,
},
Append {},
@ -143,22 +147,6 @@ impl<'bump> DiffMachine<'bump> {
}
}
/// Allows the creation of a diff machine without the concept of scopes or a virtualdom
/// this is mostly useful for testing
///
/// This will PANIC if any component elements are passed in.
pub fn new_headless(shared: &'bump SharedResources) -> Self {
todo!()
// Self {
// node_stack: smallvec![],
// mutations: Mutations::new(),
// scope_stack: smallvec![ScopeId(0)],
// vdom: shared,
// diffed: FxHashSet::default(),
// seen_scopes: FxHashSet::default(),
// }
}
//
pub async fn diff_scope(&mut self, id: ScopeId) -> Result<()> {
let component = self.get_scope_mut(&id).ok_or_else(|| Error::NotMounted)?;
@ -173,15 +161,14 @@ impl<'bump> DiffMachine<'bump> {
///
/// We do depth-first to maintain high cache locality (nodes were originally generated recursively) and because we
/// only need a stack (not a queue) of lists
///
///
///
pub async fn work(&mut self) -> Result<()> {
// todo: don't move the reused instructions around
// defer to individual functions so the compiler produces better code
// large functions tend to be difficult for the compiler to work with
while let Some(mut make) = self.node_stack.pop() {
match &mut make {
DiffInstruction::DiffNode { old, new, progress } => {
//
DiffInstruction::DiffNode { old, new, .. } => {
self.diff_node(old, new);
}
DiffInstruction::Append {} => {
@ -195,18 +182,7 @@ impl<'bump> DiffMachine<'bump> {
}
DiffInstruction::Create { node, .. } => {
// defer to individual functions so the compiler produces better code
// large functions tend to be difficult for the compiler to work with
match &node {
VNode::Text(vtext) => self.create_text_node(vtext),
VNode::Suspended(suspended) => self.create_suspended_node(suspended),
VNode::Anchor(anchor) => self.create_anchor_node(anchor),
VNode::Element(element) => self.create_element_node(element),
VNode::Fragment(frag) => self.create_fragment_node(frag),
VNode::Component(_) => {
//
}
}
self.create_node(node);
}
}
}
@ -214,6 +190,21 @@ impl<'bump> DiffMachine<'bump> {
Ok(())
}
// =================================
// Tools for creating new nodes
// =================================
fn create_node(&mut self, node: &'bump VNode<'bump>) {
match node {
VNode::Text(vtext) => self.create_text_node(vtext),
VNode::Suspended(suspended) => self.create_suspended_node(suspended),
VNode::Anchor(anchor) => self.create_anchor_node(anchor),
VNode::Element(element) => self.create_element_node(element),
VNode::Fragment(frag) => self.create_fragment_node(frag),
VNode::Component(component) => self.create_component_node(component),
}
}
fn create_text_node(&mut self, vtext: &'bump VText<'bump>) {
let real_id = self.vdom.reserve_node();
self.edit_create_text_node(vtext.text, real_id);
@ -338,459 +329,222 @@ impl<'bump> DiffMachine<'bump> {
// Push the new scope onto the stack
self.scope_stack.push(new_idx);
// Run the creation algorithm with this scope on the stack
let meta = self.create_vnode(nextnode);
todo!();
// // Run the creation algorithm with this scope on the stack
// let meta = self.create_vnode(nextnode);
// pop the scope off the stack
self.scope_stack.pop();
// // pop the scope off the stack
// self.scope_stack.pop();
if meta.added_to_stack == 0 {
panic!("Components should *always* generate nodes - even if they fail");
}
// if meta.added_to_stack == 0 {
// panic!("Components should *always* generate nodes - even if they fail");
// }
// Finally, insert this scope as a seen node.
self.seen_scopes.insert(new_idx);
// // Finally, insert this scope as a seen node.
// self.seen_scopes.insert(new_idx);
}
pub fn diff_iterative(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
match (&old_node, &new_node) {
// Handle the "sane" cases first.
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
(VNode::Text(old), VNode::Text(new)) => {
let root = old_node.direct_id();
// =================================
// Tools for diffing nodes
// =================================
if old.text != new.text {
self.edit_push_root(root);
self.edit_set_text(new.text);
self.edit_pop();
}
new.dom_id.set(Some(root));
}
(VNode::Element(old), VNode::Element(new)) => {
let root = old_node.direct_id();
// If the element type is completely different, the element needs to be re-rendered completely
// This is an optimization React makes due to how users structure their code
//
// This case is rather rare (typically only in non-keyed lists)
if new.tag_name != old.tag_name || new.namespace != old.namespace {
self.replace_node_with_node(root, old_node, new_node);
return;
}
new.dom_id.set(Some(root));
// Don't push the root if we don't have to
let mut has_comitted = false;
let mut please_commit = |edits: &mut Vec<DomEdit>| {
if !has_comitted {
has_comitted = true;
edits.push(PushRoot { id: root.as_u64() });
}
};
// Diff Attributes
//
// It's extraordinarily rare to have the number/order of attributes change
// In these cases, we just completely erase the old set and make a new set
//
// TODO: take a more efficient path than this
if old.attributes.len() == new.attributes.len() {
for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
if old_attr.value != new_attr.value {
please_commit(&mut self.mutations.edits);
self.edit_set_attribute(new_attr);
}
}
} else {
// TODO: provide some sort of report on how "good" the diffing was
please_commit(&mut self.mutations.edits);
for attribute in old.attributes {
self.edit_remove_attribute(attribute);
}
for attribute in new.attributes {
self.edit_set_attribute(attribute)
}
}
// Diff listeners
//
// It's extraordinarily rare to have the number/order of listeners change
// In the cases where the listeners change, we completely wipe the data attributes and add new ones
//
// We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
//
// TODO: take a more efficient path than this
let cur_scope: ScopeId = self.scope_stack.last().unwrap().clone();
if old.listeners.len() == new.listeners.len() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
if old_l.event != new_l.event {
please_commit(&mut self.mutations.edits);
self.edit_remove_event_listener(old_l.event);
self.edit_new_event_listener(new_l, cur_scope);
}
new_l.mounted_node.set(old_l.mounted_node.get());
self.fix_listener(new_l);
}
} else {
please_commit(&mut self.mutations.edits);
for listener in old.listeners {
self.edit_remove_event_listener(listener.event);
}
for listener in new.listeners {
listener.mounted_node.set(Some(root));
self.edit_new_event_listener(listener, cur_scope);
// Make sure the listener gets attached to the scope list
self.fix_listener(listener);
}
}
if has_comitted {
self.edit_pop();
}
self.diff_children(old.children, new.children);
}
(VNode::Component(old), VNode::Component(new)) => {
let scope_addr = old.ass_scope.get().unwrap();
// Make sure we're dealing with the same component (by function pointer)
if old.user_fc == new.user_fc {
//
self.scope_stack.push(scope_addr);
// Make sure the new component vnode is referencing the right scope id
new.ass_scope.set(Some(scope_addr));
// make sure the component's caller function is up to date
let scope = self.get_scope_mut(&scope_addr).unwrap();
scope
.update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children));
// React doesn't automatically memoize, but we do.
let compare = old.comparator.unwrap();
match compare(new) {
true => {
// the props are the same...
}
false => {
// the props are different...
scope.run_scope().unwrap();
self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
}
}
self.scope_stack.pop();
self.seen_scopes.insert(scope_addr);
} else {
let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
let first = old_iter
.next()
.expect("Components should generate a placeholder root");
// remove any leftovers
for to_remove in old_iter {
self.edit_push_root(to_remove.direct_id());
self.edit_remove();
}
// seems like we could combine this into a single instruction....
self.replace_node_with_node(first.direct_id(), old_node, new_node);
// Wipe the old one and plant the new one
let old_scope = old.ass_scope.get().unwrap();
self.destroy_scopes(old_scope);
}
}
(VNode::Fragment(old), VNode::Fragment(new)) => {
// This is the case where options or direct vnodes might be used.
// In this case, it's faster to just skip ahead to their diff
if old.children.len() == 1 && new.children.len() == 1 {
self.diff_node(&old.children[0], &new.children[0]);
return;
}
self.diff_children(old.children, new.children);
}
(VNode::Anchor(old), VNode::Anchor(new)) => {
new.dom_id.set(old.dom_id.get());
}
// The strategy here is to pick the first possible node from the previous set and use that as our replace with root
//
// We also walk the "real node" list to make sure all latent roots are claened up
// This covers the case any time a fragment or component shows up with pretty much anything else
//
// This likely isn't the fastest way to go about replacing one node with a virtual node, but the "insane" cases
// are pretty rare. IE replacing a list (component or fragment) with a single node.
(
VNode::Component(_)
| VNode::Fragment(_)
| VNode::Text(_)
| VNode::Element(_)
| VNode::Anchor(_),
VNode::Component(_)
| VNode::Fragment(_)
| VNode::Text(_)
| VNode::Element(_)
| VNode::Anchor(_),
) => {
self.replace_and_create_many_with_many([old_node], [new_node]);
}
// TODO
(VNode::Suspended(old), new) => {
//
self.replace_and_create_many_with_many([old_node], [new_node]);
}
// a node that was once real is now suspended
(old, VNode::Suspended(_)) => {
//
self.replace_and_create_many_with_many([old_node], [new_node]);
}
}
}
// Diff the `old` node with the `new` node. Emits instructions to modify a
// physical DOM node that reflects `old` into something that reflects `new`.
//
// the real stack should be what it is coming in and out of this function (ideally empty)
//
// each function call assumes the stack is fresh (empty).
pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
match (&old_node, &new_node) {
// Handle the "sane" cases first.
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
(VNode::Text(old), VNode::Text(new)) => {
let root = old_node.direct_id();
use VNode::*;
match (old_node, new_node) {
// Check the most common cases first
(Text(old), Text(new)) => self.diff_text_nodes(old, new),
(Element(old), Element(new)) => self.diff_element_nodes(old, new),
(Component(old), Component(new)) => self.diff_component_nodes(old, new),
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
(Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()),
if old.text != new.text {
self.edit_push_root(root);
self.edit_set_text(new.text);
self.edit_pop();
}
new.dom_id.set(Some(root));
}
(VNode::Element(old), VNode::Element(new)) => {
let root = old_node.direct_id();
// If the element type is completely different, the element needs to be re-rendered completely
// This is an optimization React makes due to how users structure their code
//
// This case is rather rare (typically only in non-keyed lists)
if new.tag_name != old.tag_name || new.namespace != old.namespace {
self.replace_node_with_node(root, old_node, new_node);
return;
}
new.dom_id.set(Some(root));
// Don't push the root if we don't have to
let mut has_comitted = false;
let mut please_commit = |edits: &mut Vec<DomEdit>| {
if !has_comitted {
has_comitted = true;
edits.push(PushRoot { id: root.as_u64() });
}
};
// Diff Attributes
//
// It's extraordinarily rare to have the number/order of attributes change
// In these cases, we just completely erase the old set and make a new set
//
// TODO: take a more efficient path than this
if old.attributes.len() == new.attributes.len() {
for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
if old_attr.value != new_attr.value {
please_commit(&mut self.mutations.edits);
self.edit_set_attribute(new_attr);
}
}
} else {
// TODO: provide some sort of report on how "good" the diffing was
please_commit(&mut self.mutations.edits);
for attribute in old.attributes {
self.edit_remove_attribute(attribute);
}
for attribute in new.attributes {
self.edit_set_attribute(attribute)
}
}
// Diff listeners
//
// It's extraordinarily rare to have the number/order of listeners change
// In the cases where the listeners change, we completely wipe the data attributes and add new ones
//
// We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
//
// TODO: take a more efficient path than this
let cur_scope: ScopeId = self.scope_stack.last().unwrap().clone();
if old.listeners.len() == new.listeners.len() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
if old_l.event != new_l.event {
please_commit(&mut self.mutations.edits);
self.edit_remove_event_listener(old_l.event);
self.edit_new_event_listener(new_l, cur_scope);
}
new_l.mounted_node.set(old_l.mounted_node.get());
self.fix_listener(new_l);
}
} else {
please_commit(&mut self.mutations.edits);
for listener in old.listeners {
self.edit_remove_event_listener(listener.event);
}
for listener in new.listeners {
listener.mounted_node.set(Some(root));
self.edit_new_event_listener(listener, cur_scope);
// Make sure the listener gets attached to the scope list
self.fix_listener(listener);
}
}
if has_comitted {
self.edit_pop();
}
self.diff_children(old.children, new.children);
}
(VNode::Component(old), VNode::Component(new)) => {
let scope_addr = old.ass_scope.get().unwrap();
// Make sure we're dealing with the same component (by function pointer)
if old.user_fc == new.user_fc {
//
self.scope_stack.push(scope_addr);
// Make sure the new component vnode is referencing the right scope id
new.ass_scope.set(Some(scope_addr));
// make sure the component's caller function is up to date
let scope = self.get_scope_mut(&scope_addr).unwrap();
scope
.update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children));
// React doesn't automatically memoize, but we do.
let compare = old.comparator.unwrap();
match compare(new) {
true => {
// the props are the same...
}
false => {
// the props are different...
scope.run_scope().unwrap();
self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
}
}
self.scope_stack.pop();
self.seen_scopes.insert(scope_addr);
} else {
let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
let first = old_iter
.next()
.expect("Components should generate a placeholder root");
// remove any leftovers
for to_remove in old_iter {
self.edit_push_root(to_remove.direct_id());
self.edit_remove();
}
// seems like we could combine this into a single instruction....
self.replace_node_with_node(first.direct_id(), old_node, new_node);
// Wipe the old one and plant the new one
let old_scope = old.ass_scope.get().unwrap();
self.destroy_scopes(old_scope);
}
}
(VNode::Fragment(old), VNode::Fragment(new)) => {
// This is the case where options or direct vnodes might be used.
// In this case, it's faster to just skip ahead to their diff
if old.children.len() == 1 && new.children.len() == 1 {
self.diff_node(&old.children[0], &new.children[0]);
return;
}
self.diff_children(old.children, new.children);
}
(VNode::Anchor(old), VNode::Anchor(new)) => {
new.dom_id.set(old.dom_id.get());
}
// The strategy here is to pick the first possible node from the previous set and use that as our replace with root
//
// We also walk the "real node" list to make sure all latent roots are claened up
// This covers the case any time a fragment or component shows up with pretty much anything else
//
// This likely isn't the fastest way to go about replacing one node with a virtual node, but the "insane" cases
// are pretty rare. IE replacing a list (component or fragment) with a single node.
(
VNode::Component(_)
| VNode::Fragment(_)
| VNode::Text(_)
| VNode::Element(_)
| VNode::Anchor(_),
VNode::Component(_)
| VNode::Fragment(_)
| VNode::Text(_)
| VNode::Element(_)
| VNode::Anchor(_),
Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_),
Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_),
) => {
self.replace_and_create_many_with_many([old_node], [new_node]);
}
// TODO
(VNode::Suspended(old), new) => {
//
// TODO: these don't properly clean up any data
(Suspended(old), new) => {
self.replace_and_create_many_with_many([old_node], [new_node]);
}
// a node that was once real is now suspended
(old, VNode::Suspended(_)) => {
//
(old, Suspended(_)) => {
self.replace_and_create_many_with_many([old_node], [new_node]);
}
}
}
fn create_children(&mut self, children: &'bump [VNode<'bump>]) -> CreateMeta {
let mut is_static = true;
let mut added_to_stack = 0;
fn diff_text_nodes(&mut self, old: &'bump VText<'bump>, new: &'bump VText<'bump>) {
let root = old.dom_id.get().unwrap();
// add them backwards
for child in children.iter().rev() {
let child_meta = self.create_vnode(child);
is_static = is_static && child_meta.is_static;
added_to_stack += child_meta.added_to_stack;
if old.text != new.text {
self.edit_push_root(root);
self.edit_set_text(new.text);
self.edit_pop();
}
CreateMeta {
is_static,
added_to_stack,
new.dom_id.set(Some(root));
}
fn diff_element_nodes(&mut self, old: &'bump VElement<'bump>, new: &'bump VElement<'bump>) {
let root = old.dom_id.get().unwrap();
// If the element type is completely different, the element needs to be re-rendered completely
// This is an optimization React makes due to how users structure their code
//
// This case is rather rare (typically only in non-keyed lists)
if new.tag_name != old.tag_name || new.namespace != old.namespace {
self.replace_node_with_node(root, old_node, new_node);
return;
}
new.dom_id.set(Some(root));
// Don't push the root if we don't have to
let mut has_comitted = false;
let mut please_commit = |edits: &mut Vec<DomEdit>| {
if !has_comitted {
has_comitted = true;
edits.push(PushRoot { id: root.as_u64() });
}
};
// Diff Attributes
//
// It's extraordinarily rare to have the number/order of attributes change
// In these cases, we just completely erase the old set and make a new set
//
// TODO: take a more efficient path than this
if old.attributes.len() == new.attributes.len() {
for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
if old_attr.value != new_attr.value {
please_commit(&mut self.mutations.edits);
self.edit_set_attribute(new_attr);
}
}
} else {
// TODO: provide some sort of report on how "good" the diffing was
please_commit(&mut self.mutations.edits);
for attribute in old.attributes {
self.edit_remove_attribute(attribute);
}
for attribute in new.attributes {
self.edit_set_attribute(attribute)
}
}
// Diff listeners
//
// It's extraordinarily rare to have the number/order of listeners change
// In the cases where the listeners change, we completely wipe the data attributes and add new ones
//
// We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
//
// TODO: take a more efficient path than this
let cur_scope: ScopeId = self.scope_stack.last().unwrap().clone();
if old.listeners.len() == new.listeners.len() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
if old_l.event != new_l.event {
please_commit(&mut self.mutations.edits);
self.edit_remove_event_listener(old_l.event);
self.edit_new_event_listener(new_l, cur_scope);
}
new_l.mounted_node.set(old_l.mounted_node.get());
self.fix_listener(new_l);
}
} else {
please_commit(&mut self.mutations.edits);
for listener in old.listeners {
self.edit_remove_event_listener(listener.event);
}
for listener in new.listeners {
listener.mounted_node.set(Some(root));
self.edit_new_event_listener(listener, cur_scope);
// Make sure the listener gets attached to the scope list
self.fix_listener(listener);
}
}
if has_comitted {
self.edit_pop();
}
self.diff_children(old.children, new.children);
}
fn diff_component_nodes(
&mut self,
old: &'bump VComponent<'bump>,
new: &'bump VComponent<'bump>,
) {
let scope_addr = old.ass_scope.get().unwrap();
// Make sure we're dealing with the same component (by function pointer)
if old.user_fc == new.user_fc {
//
self.scope_stack.push(scope_addr);
// Make sure the new component vnode is referencing the right scope id
new.ass_scope.set(Some(scope_addr));
// make sure the component's caller function is up to date
let scope = self.get_scope_mut(&scope_addr).unwrap();
scope.update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children));
// React doesn't automatically memoize, but we do.
let compare = old.comparator.unwrap();
match compare(new) {
true => {
// the props are the same...
}
false => {
// the props are different...
scope.run_scope().unwrap();
self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
}
}
self.scope_stack.pop();
self.seen_scopes.insert(scope_addr);
} else {
todo!();
// let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
// let first = old_iter
// .next()
// .expect("Components should generate a placeholder root");
// // remove any leftovers
// for to_remove in old_iter {
// self.edit_push_root(to_remove.direct_id());
// self.edit_remove();
// }
// // seems like we could combine this into a single instruction....
// self.replace_node_with_node(first.direct_id(), old_node, new_node);
// // Wipe the old one and plant the new one
// let old_scope = old.ass_scope.get().unwrap();
// self.destroy_scopes(old_scope);
}
}
fn diff_fragment_nodes(&mut self, old: &'bump VFragment<'bump>, new: &'bump VFragment<'bump>) {
// This is the case where options or direct vnodes might be used.
// In this case, it's faster to just skip ahead to their diff
if old.children.len() == 1 && new.children.len() == 1 {
self.diff_node(&old.children[0], &new.children[0]);
return;
}
self.diff_children(old.children, new.children);
}
/// Destroy a scope and all of its descendents.

View file

@ -159,17 +159,19 @@ impl VirtualDom {
.get_scope_mut(&self.base_scope)
.expect("The base scope should never be moved");
// We run the component. If it succeeds, then we can diff it and add the changes to the dom.
if cur_component.run_scope().is_ok() {
let meta = diff_machine.create_vnode(cur_component.frames.fin_head());
diff_machine.edit_append_children(meta.added_to_stack);
} else {
// todo: should this be a hard error?
log::warn!(
"Component failed to run succesfully during rebuild.
This does not result in a failed rebuild, but indicates a logic failure within your app."
);
}
todo!();
// // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
// if cur_component.run_scope().is_ok() {
// let meta = diff_machine.create_vnode(cur_component.frames.fin_head());
// diff_machine.edit_append_children(meta.added_to_stack);
// } else {
// // todo: should this be a hard error?
// log::warn!(
// "Component failed to run succesfully during rebuild.
// This does not result in a failed rebuild, but indicates a logic failure within your app."
// );
// }
Ok(diff_machine.mutations)
}

View file

@ -1,3 +1,7 @@
//! Utility Code taken from async_std to immediately yield the current task to the executor.
//!
//!
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

View file

@ -45,3 +45,13 @@ async fn test_iterative_diff() {
let mut machine = DiffMachine::new_headless(&shared);
let a = machine.work().await.unwrap();
}
#[async_std::test]
async fn websys_loop() {
///loop {
/// let deadline = request_idle_callback().await;
/// let edits = dom.work(deadline);
/// request_animation_frame().await;
/// apply(edits);
///}
}