wip: tests added for new iterative

This commit is contained in:
Jonathan Kelley committed 2021-08-21 13:24:47 -04:00
1 parent 2ce0752a9c
commit 24572b63de
6 files changed
+191 -69

No files matched your search

+2
View File
@@ -50,6 +50,8 @@ indexmap = "1.7.0"
anyhow = "1.0.42" anyhow = "1.0.42"
async-std = { version = "1.9.0", features = ["attributes"] } async-std = { version = "1.9.0", features = ["attributes"] }
dioxus-html = { path = "../html" } dioxus-html = { path = "../html" }
fern = { version = "0.6.0", features = ["colored"] }
simple_logger = "1.13.0"
[features] [features]
+55 -41
View File
@@ -100,7 +100,7 @@ pub struct DiffMachine<'bump> {
pub nodes_created_stack: SmallVec<[usize; 10]>, pub nodes_created_stack: SmallVec<[usize; 10]>,
pub node_stack: SmallVec<[DiffInstruction<'bump>; 10]>, pub instructions: SmallVec<[DiffInstruction<'bump>; 10]>,
pub scope_stack: SmallVec<[ScopeId; 5]>, pub scope_stack: SmallVec<[ScopeId; 5]>,
@@ -113,6 +113,7 @@ pub struct DiffMachine<'bump> {
/// ///
/// Right now, we insert an instruction for every child node we want to create and diff. This can be less efficient than /// Right now, we insert an instruction for every child node we want to create and diff. This can be less efficient than
/// a custom iterator type - but this is current easier to implement. In the future, let's try interact with the stack less. /// a custom iterator type - but this is current easier to implement. In the future, let's try interact with the stack less.
#[derive(Debug)]
pub enum DiffInstruction<'a> { pub enum DiffInstruction<'a> {
DiffNode { DiffNode {
old: &'a VNode<'a>, old: &'a VNode<'a>,
@@ -121,7 +122,8 @@ pub enum DiffInstruction<'a> {
DiffChildren { DiffChildren {
progress: usize, progress: usize,
children: &'a [VNode<'a>], old: &'a [VNode<'a>],
new: &'a [VNode<'a>],
}, },
Create { Create {
@@ -148,7 +150,7 @@ impl<'bump> DiffMachine<'bump> {
shared: &'bump SharedResources, shared: &'bump SharedResources,
) -> Self { ) -> Self {
Self { Self {
node_stack: smallvec![], instructions: smallvec![],
nodes_created_stack: smallvec![], nodes_created_stack: smallvec![],
mutations: edits, mutations: edits,
scope_stack: smallvec![cur_scope], scope_stack: smallvec![cur_scope],
@@ -158,6 +160,12 @@ impl<'bump> DiffMachine<'bump> {
} }
} }
pub fn new_headless(shared: &'bump SharedResources) -> Self {
let edits = Mutations::new();
let cur_scope = ScopeId(0);
Self::new(edits, cur_scope, shared)
}
// //
pub async fn diff_scope(&mut self, id: ScopeId) -> Result<()> { pub async fn diff_scope(&mut self, id: ScopeId) -> Result<()> {
let component = self.get_scope_mut(&id).ok_or_else(|| Error::NotMounted)?; let component = self.get_scope_mut(&id).ok_or_else(|| Error::NotMounted)?;
@@ -176,50 +184,60 @@ impl<'bump> DiffMachine<'bump> {
// todo: don't move the reused instructions around // todo: don't move the reused instructions around
// defer to individual functions so the compiler produces better code // defer to individual functions so the compiler produces better code
// large functions tend to be difficult for the compiler to work with // large functions tend to be difficult for the compiler to work with
let mut should_pop = false; while let Some(instruction) = self.instructions.last_mut() {
while let Some(make) = self.node_stack.last_mut() { log::debug!("Handling diff instruction: {:?}", instruction);
should_pop = false; match instruction {
match make {
DiffInstruction::DiffNode { old, new, .. } => { DiffInstruction::DiffNode { old, new, .. } => {
let (old, new) = (*old, *new); let (old, new) = (*old, *new);
self.instructions.pop();
self.diff_node(old, new); self.diff_node(old, new);
} }
DiffInstruction::DiffChildren { progress, children } => { // this is slightly more complicated, we need to find a way to pause our LIS code
// DiffInstruction::DiffChildren { progress, old, new } => {
todo!()
} }
DiffInstruction::Create { node, .. } => { DiffInstruction::Create { node, .. } => {
let node = *node; let node = *node;
self.instructions.pop();
self.create_node(node); self.create_node(node);
} }
DiffInstruction::CreateChildren { progress, children } => { DiffInstruction::CreateChildren { progress, children } => {
if let Some(child) = (children).get(*progress) { if let Some(child) = children.get(*progress) {
*progress += 1; *progress += 1;
self.create_node(child); self.create_node(child);
} else { } else {
should_pop = true; self.instructions.pop();
} }
} }
DiffInstruction::Append {} => { DiffInstruction::Append => {
let many = self.nodes_created_stack.pop().unwrap(); let many = self.nodes_created_stack.pop().unwrap();
self.edit_append_children(many as u32); self.edit_append_children(many as u32);
self.instructions.pop();
} }
DiffInstruction::Replace { with } => { DiffInstruction::Replace { with } => {
let with = *with; let with = *with;
let many = self.nodes_created_stack.pop().unwrap(); let many = self.nodes_created_stack.pop().unwrap();
self.edit_replace_with(with as u32, many as u32); self.edit_replace_with(with as u32, many as u32);
self.instructions.pop();
} }
DiffInstruction::InsertAfter => todo!(), DiffInstruction::InsertAfter => {
DiffInstruction::InsertBefore => todo!(), let n = self.nodes_created_stack.pop().unwrap();
} self.edit_insert_after(n as u32);
if should_pop { self.instructions.pop();
self.node_stack.pop(); }
}
DiffInstruction::InsertBefore => {
let n = self.nodes_created_stack.pop().unwrap();
self.edit_insert_before(n as u32);
self.instructions.pop();
}
};
} }
Ok(()) Ok(())
@@ -268,15 +286,13 @@ impl<'bump> DiffMachine<'bump> {
attributes, attributes,
children, children,
namespace, namespace,
static_attrs: _,
static_children: _,
static_listeners: _,
dom_id, dom_id,
key, ..
} = element; } = element;
let real_id = self.vdom.reserve_node(); let real_id = self.vdom.reserve_node();
self.edit_create_element(tag_name, *namespace, real_id); self.edit_create_element(tag_name, *namespace, real_id);
*self.nodes_created_stack.last_mut().unwrap() += 1;
dom_id.set(Some(real_id)); dom_id.set(Some(real_id));
let cur_scope = self.current_scope().unwrap(); let cur_scope = self.current_scope().unwrap();
@@ -285,30 +301,26 @@ impl<'bump> DiffMachine<'bump> {
self.fix_listener(listener); self.fix_listener(listener);
listener.mounted_node.set(Some(real_id)); listener.mounted_node.set(Some(real_id));
self.edit_new_event_listener(listener, cur_scope.clone()); self.edit_new_event_listener(listener, cur_scope.clone());
// if the node has an event listener, then it must be visited ?
}); });
for attr in *attributes { for attr in *attributes {
self.edit_set_attribute(attr); self.edit_set_attribute(attr);
} }
// TODO: the append child edit self.instructions.push(DiffInstruction::Append);
*self.nodes_created_stack.last_mut().unwrap() += 1;
// push every child onto the stack
self.nodes_created_stack.push(0); self.nodes_created_stack.push(0);
for child in *children { self.instructions.push(DiffInstruction::CreateChildren {
self.node_stack children,
.push(DiffInstruction::Create { node: child }) progress: 0,
} });
} }
fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) { fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) {
for node in frag.children { self.instructions.push(DiffInstruction::CreateChildren {
self.node_stack.push(DiffInstruction::Create { node }) children: frag.children,
} progress: 0,
});
} }
fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) { fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) {
@@ -348,7 +360,7 @@ impl<'bump> DiffMachine<'bump> {
// Run the scope for one iteration to initialize it // Run the scope for one iteration to initialize it
match new_component.run_scope() { match new_component.run_scope() {
Ok(_) => { Ok(_g) => {
// all good, new nodes exist // all good, new nodes exist
} }
Err(err) => { Err(err) => {
@@ -361,11 +373,13 @@ impl<'bump> DiffMachine<'bump> {
// Take the node that was just generated from running the component // Take the node that was just generated from running the component
let nextnode = new_component.frames.fin_head(); let nextnode = new_component.frames.fin_head();
// Push the new scope onto the stack // // Push the new scope onto the stack
self.scope_stack.push(new_idx); // self.scope_stack.push(new_idx);
todo!();
// // Run the creation algorithm with this scope on the stack // // Run the creation algorithm with this scope on the stack
self.instructions
.push(DiffInstruction::Create { node: nextnode });
// let meta = self.create_vnode(nextnode); // let meta = self.create_vnode(nextnode);
// // pop the scope off the stack // // pop the scope off the stack
@@ -376,7 +390,7 @@ impl<'bump> DiffMachine<'bump> {
// } // }
// // Finally, insert this scope as a seen node. // // Finally, insert this scope as a seen node.
// self.seen_scopes.insert(new_idx); self.seen_scopes.insert(new_idx);
} }
// ================================= // =================================
+6 -6
View File
@@ -38,12 +38,12 @@ pub enum VNode<'src> {
impl<'src> VNode<'src> { impl<'src> VNode<'src> {
pub fn key(&self) -> Option<&'src str> { pub fn key(&self) -> Option<&'src str> {
match &self { match &self {
VNode::Text(_) => todo!(), VNode::Element(el) => el.key,
VNode::Element(_) => todo!(), VNode::Component(c) => c.key,
VNode::Fragment(_) => todo!(), VNode::Fragment(f) => f.key,
VNode::Component(_) => todo!(), VNode::Text(_t) => None,
VNode::Suspended(_) => todo!(), VNode::Suspended(s) => None,
VNode::Anchor(_) => todo!(), VNode::Anchor(f) => None,
} }
} }
pub fn direct_id(&self) -> ElementId { pub fn direct_id(&self) -> ElementId {
+41 -11
View File
@@ -159,23 +159,53 @@ impl VirtualDom {
.get_scope_mut(&self.base_scope) .get_scope_mut(&self.base_scope)
.expect("The base scope should never be moved"); .expect("The base scope should never be moved");
todo!(); // todo!();
// // We run the component. If it succeeds, then we can diff it and add the changes to the dom. // // 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() { if cur_component.run_scope().is_ok() {
// let meta = diff_machine.create_vnode(cur_component.frames.fin_head()); diff_machine.nodes_created_stack.push(0);
// diff_machine.edit_append_children(meta.added_to_stack); diff_machine.instructions.push(DiffInstruction::Create {
// } else { node: cur_component.frames.fin_head(),
// // todo: should this be a hard error? });
// log::warn!( } else {
// "Component failed to run succesfully during rebuild. // todo: should this be a hard error?
// This does not result in a failed rebuild, but indicates a logic failure within your app." 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) Ok(diff_machine.mutations)
} }
pub async fn rebuild_async<'s>(&'s mut self) -> Result<Mutations<'s>> {
let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.shared);
let cur_component = diff_machine
.get_scope_mut(&self.base_scope)
.expect("The base scope should never be moved");
// 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() {
diff_machine.nodes_created_stack.push(0);
diff_machine.instructions.push(DiffInstruction::Create {
node: cur_component.frames.fin_head(),
});
diff_machine.work().await.unwrap();
let many = diff_machine.nodes_created_stack.pop().unwrap() as u32;
diff_machine.edit_append_children(many);
} 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)
}
/// Runs the virtualdom immediately, not waiting for any suspended nodes to complete. /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
/// ///
/// This method will not wait for any suspended nodes to complete. /// This method will not wait for any suspended nodes to complete.
+38 -11
View File
@@ -8,6 +8,7 @@ use dioxus::{
scheduler::Mutations, scheduler::Mutations,
DomEdit, DomEdit,
}; };
mod test_logging;
use dioxus_core as dioxus; use dioxus_core as dioxus;
use dioxus_html as dioxus_elements; use dioxus_html as dioxus_elements;
@@ -29,29 +30,55 @@ fn test_original_diff() {
} }
#[async_std::test] #[async_std::test]
async fn test_iterative_diff() { async fn test_iterative_create() {
static App: FC<()> = |cx| { static App: FC<()> = |cx| {
cx.render(rsx! { cx.render(rsx! {
div { div {
div { div {
"Hello, world!" "Hello, world!"
div {
div {
Fragment {
"hello"
"world"
}
}
}
} }
} }
}) })
}; };
let shared = SharedResources::new(); test_logging::set_up_logging();
let mut machine = DiffMachine::new_headless(&shared); let mut dom = VirtualDom::new(App);
let a = machine.work().await.unwrap(); let mutations = dom.rebuild_async().await.unwrap();
dbg!(mutations);
} }
#[async_std::test] #[async_std::test]
async fn websys_loop() { async fn test_iterative_create_list() {
///loop { static App: FC<()> = |cx| {
/// let deadline = request_idle_callback().await; cx.render(rsx! {
/// let edits = dom.work(deadline); {(0..10).map(|f| rsx!{ div {
/// request_animation_frame().await; "hello"
/// apply(edits); }})}
///} })
};
test_logging::set_up_logging();
let mut dom = VirtualDom::new(App);
let mutations = dom.rebuild_async().await.unwrap();
dbg!(mutations);
} }
// #[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);
// ///}
// }
+49
View File
@@ -0,0 +1,49 @@
pub fn set_up_logging() {
use fern::colors::{Color, ColoredLevelConfig};
use log::debug;
// configure colors for the whole line
let colors_line = ColoredLevelConfig::new()
.error(Color::Red)
.warn(Color::Yellow)
// we actually don't need to specify the color for debug and info, they are white by default
.info(Color::White)
.debug(Color::White)
// depending on the terminals color scheme, this is the same as the background color
.trace(Color::BrightBlack);
// configure colors for the name of the level.
// since almost all of them are the same as the color for the whole line, we
// just clone `colors_line` and overwrite our changes
let colors_level = colors_line.clone().info(Color::Green);
// here we set up our fern Dispatch
fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
"{color_line}[{level}{color_line}] {message}\x1B[0m",
color_line = format_args!(
"\x1B[{}m",
colors_line.get_color(&record.level()).to_fg_str()
),
level = colors_level.color(record.level()),
message = message,
));
})
// set the default log level. to filter out verbose log messages from dependencies, set
// this to Warn and overwrite the log level for your crate.
.level(log::LevelFilter::Debug)
// .level(log::LevelFilter::Warn)
// change log levels for individual modules. Note: This looks for the record's target
// field which defaults to the module path but can be overwritten with the `target`
// parameter:
// `info!(target="special_target", "This log message is about special_target");`
// .level_for("dioxus", log::LevelFilter::Debug)
// .level_for("dioxus", log::LevelFilter::Info)
// .level_for("pretty_colored", log::LevelFilter::Trace)
// output to stdout
.chain(std::io::stdout())
.apply()
.unwrap();
debug!("finished setting up logging! yay!");
}