diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index d0da2ff07..3a0c66c02 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -50,6 +50,8 @@ indexmap = "1.7.0" anyhow = "1.0.42" async-std = { version = "1.9.0", features = ["attributes"] } dioxus-html = { path = "../html" } +fern = { version = "0.6.0", features = ["colored"] } +simple_logger = "1.13.0" [features] diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 828c210c4..1c193d7e4 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -100,7 +100,7 @@ pub struct DiffMachine<'bump> { 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]>, @@ -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 /// 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> { DiffNode { old: &'a VNode<'a>, @@ -121,7 +122,8 @@ pub enum DiffInstruction<'a> { DiffChildren { progress: usize, - children: &'a [VNode<'a>], + old: &'a [VNode<'a>], + new: &'a [VNode<'a>], }, Create { @@ -148,7 +150,7 @@ impl<'bump> DiffMachine<'bump> { shared: &'bump SharedResources, ) -> Self { Self { - node_stack: smallvec![], + instructions: smallvec![], nodes_created_stack: smallvec![], mutations: edits, 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<()> { 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 // defer to individual functions so the compiler produces better code // large functions tend to be difficult for the compiler to work with - let mut should_pop = false; - while let Some(make) = self.node_stack.last_mut() { - should_pop = false; - match make { + while let Some(instruction) = self.instructions.last_mut() { + log::debug!("Handling diff instruction: {:?}", instruction); + match instruction { DiffInstruction::DiffNode { old, new, .. } => { let (old, new) = (*old, *new); + self.instructions.pop(); 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, .. } => { let node = *node; + self.instructions.pop(); self.create_node(node); } + DiffInstruction::CreateChildren { progress, children } => { - if let Some(child) = (children).get(*progress) { + if let Some(child) = children.get(*progress) { *progress += 1; self.create_node(child); } else { - should_pop = true; + self.instructions.pop(); } } - DiffInstruction::Append {} => { + DiffInstruction::Append => { let many = self.nodes_created_stack.pop().unwrap(); self.edit_append_children(many as u32); + self.instructions.pop(); } DiffInstruction::Replace { with } => { let with = *with; let many = self.nodes_created_stack.pop().unwrap(); - self.edit_replace_with(with as u32, many as u32); + self.instructions.pop(); } - DiffInstruction::InsertAfter => todo!(), - DiffInstruction::InsertBefore => todo!(), - } - if should_pop { - self.node_stack.pop(); - } + DiffInstruction::InsertAfter => { + let n = self.nodes_created_stack.pop().unwrap(); + self.edit_insert_after(n as u32); + self.instructions.pop(); + } + + DiffInstruction::InsertBefore => { + let n = self.nodes_created_stack.pop().unwrap(); + self.edit_insert_before(n as u32); + self.instructions.pop(); + } + }; } Ok(()) @@ -268,15 +286,13 @@ impl<'bump> DiffMachine<'bump> { attributes, children, namespace, - static_attrs: _, - static_children: _, - static_listeners: _, dom_id, - key, + .. } = element; let real_id = self.vdom.reserve_node(); self.edit_create_element(tag_name, *namespace, real_id); + *self.nodes_created_stack.last_mut().unwrap() += 1; dom_id.set(Some(real_id)); let cur_scope = self.current_scope().unwrap(); @@ -285,30 +301,26 @@ impl<'bump> DiffMachine<'bump> { self.fix_listener(listener); listener.mounted_node.set(Some(real_id)); 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 { 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); - for child in *children { - self.node_stack - .push(DiffInstruction::Create { node: child }) - } + self.instructions.push(DiffInstruction::CreateChildren { + children, + progress: 0, + }); } fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) { - for node in frag.children { - self.node_stack.push(DiffInstruction::Create { node }) - } + self.instructions.push(DiffInstruction::CreateChildren { + children: frag.children, + progress: 0, + }); } 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 match new_component.run_scope() { - Ok(_) => { + Ok(_g) => { // all good, new nodes exist } Err(err) => { @@ -361,11 +373,13 @@ impl<'bump> DiffMachine<'bump> { // Take the node that was just generated from running the component let nextnode = new_component.frames.fin_head(); - // Push the new scope onto the stack - self.scope_stack.push(new_idx); + // // Push the new scope onto the stack + // self.scope_stack.push(new_idx); - todo!(); // // Run the creation algorithm with this scope on the stack + self.instructions + .push(DiffInstruction::Create { node: nextnode }); + // let meta = self.create_vnode(nextnode); // // pop the scope off the stack @@ -376,7 +390,7 @@ impl<'bump> DiffMachine<'bump> { // } // // Finally, insert this scope as a seen node. - // self.seen_scopes.insert(new_idx); + self.seen_scopes.insert(new_idx); } // ================================= diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 865284794..05798f1ac 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -38,12 +38,12 @@ pub enum VNode<'src> { impl<'src> VNode<'src> { pub fn key(&self) -> Option<&'src str> { match &self { - VNode::Text(_) => todo!(), - VNode::Element(_) => todo!(), - VNode::Fragment(_) => todo!(), - VNode::Component(_) => todo!(), - VNode::Suspended(_) => todo!(), - VNode::Anchor(_) => todo!(), + VNode::Element(el) => el.key, + VNode::Component(c) => c.key, + VNode::Fragment(f) => f.key, + VNode::Text(_t) => None, + VNode::Suspended(s) => None, + VNode::Anchor(f) => None, } } pub fn direct_id(&self) -> ElementId { diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 195dafa8b..23b43c210 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -159,23 +159,53 @@ impl VirtualDom { .get_scope_mut(&self.base_scope) .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. - // 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." - // ); - // } + 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(), + }); + } 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) } + pub async fn rebuild_async<'s>(&'s mut self) -> Result> { + 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. /// /// This method will not wait for any suspended nodes to complete. diff --git a/packages/core/tests/iterative.rs b/packages/core/tests/iterative.rs index 6ec318e61..27fe91e92 100644 --- a/packages/core/tests/iterative.rs +++ b/packages/core/tests/iterative.rs @@ -8,6 +8,7 @@ use dioxus::{ scheduler::Mutations, DomEdit, }; +mod test_logging; use dioxus_core as dioxus; use dioxus_html as dioxus_elements; @@ -29,29 +30,55 @@ fn test_original_diff() { } #[async_std::test] -async fn test_iterative_diff() { +async fn test_iterative_create() { static App: FC<()> = |cx| { cx.render(rsx! { div { div { "Hello, world!" + div { + div { + Fragment { + "hello" + "world" + } + } + } } } }) }; - let shared = SharedResources::new(); + test_logging::set_up_logging(); - let mut machine = DiffMachine::new_headless(&shared); - let a = machine.work().await.unwrap(); + 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); - ///} +async fn test_iterative_create_list() { + static App: FC<()> = |cx| { + cx.render(rsx! { + {(0..10).map(|f| rsx!{ div { + "hello" + }})} + }) + }; + + 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); +// ///} +// } diff --git a/packages/core/tests/test_logging.rs b/packages/core/tests/test_logging.rs new file mode 100644 index 000000000..a8cf6926b --- /dev/null +++ b/packages/core/tests/test_logging.rs @@ -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!"); +}