From 882d69571dcc807a5fa3734f14980e5233f895f5 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 15 Aug 2021 10:13:03 -0400 Subject: [PATCH] wip: more work on scheduler --- packages/core/Cargo.toml | 1 + packages/core/src/scheduler.rs | 468 +++++++++++++--------------- packages/core/src/virtual_dom.rs | 53 +++- packages/core/tests/hooks.rs | 26 +- packages/core/tests/vdom_rebuild.rs | 16 +- 5 files changed, 268 insertions(+), 296 deletions(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 2ad42bccf..d0da2ff07 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -43,6 +43,7 @@ futures-channel = "0.3.16" # used for noderefs once_cell = "1.8.0" +indexmap = "1.7.0" [dev-dependencies] diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index 07030a2bc..364f76609 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -5,11 +5,18 @@ use std::cell::{Ref, RefCell, RefMut}; use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque}; use std::pin::Pin; +use futures_util::future::FusedFuture; +use futures_util::pin_mut; use futures_util::Future; +use futures_util::FutureExt; +use futures_util::StreamExt; +use indexmap::IndexSet; use crate::innerlude::*; /// The "Mutations" object holds the changes that need to be made to the DOM. +/// +#[derive(Debug)] pub struct Mutations<'s> { pub edits: Vec>, pub noderefs: Vec>, @@ -21,6 +28,8 @@ impl<'s> Mutations<'s> { let noderefs = Vec::new(); Self { edits, noderefs } } + + pub fn extend(&mut self, other: &mut Mutations) {} } // refs are only assigned once @@ -29,6 +38,14 @@ pub struct NodeRefMutation<'a> { element_id: ElementId, } +impl<'a> std::fmt::Debug for NodeRefMutation<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NodeRefMutation") + .field("element_id", &self.element_id) + .finish() + } +} + impl<'a> NodeRefMutation<'a> { pub fn downcast_ref(&self) -> Option<&T> { self.element @@ -48,16 +65,18 @@ pub struct Scheduler { current_priority: EventPriority, pending_events: VecDeque, - pending_immediates: VecDeque, + + pending_immediates: VecDeque, + pending_tasks: VecDeque, garbage_scopes: HashSet, shared: SharedResources, - dirty_scopes: [HashSet; 3], - - fibers: Vec>, + high_priorty: PriorityFiber<'static>, + medium_priority: PriorityFiber<'static>, + low_priority: PriorityFiber<'static>, } pub enum FiberResult<'a> { @@ -75,14 +94,13 @@ impl Scheduler { pending_immediates: VecDeque::new(), pending_tasks: VecDeque::new(), - fibers: Vec::new(), - garbage_scopes: HashSet::new(), current_priority: EventPriority::Low, - // low, medium, high - dirty_scopes: [HashSet::new(), HashSet::new(), HashSet::new()], + high_priorty: PriorityFiber::new(), + medium_priority: PriorityFiber::new(), + low_priority: PriorityFiber::new(), } } @@ -99,142 +117,206 @@ impl Scheduler { }) } - pub fn manually_poll_channels(&mut self) { - todo!() + pub fn manually_poll_events(&mut self) { + // 1. Poll the UI event receiver + // 2. Poll the set_state receiver + + // poll the immediates first, creating work out of them + let shared_receiver = self.shared.immediate_receiver.clone(); + let mut receiver = shared_receiver.borrow_mut(); + while let Ok(Some(trigger)) = receiver.try_next() { + self.add_dirty_scope(trigger, EventPriority::Low); + } + + // next poll the UI events, + let mut receiver = self.shared.ui_event_receiver.borrow_mut(); + while let Ok(Some(trigger)) = receiver.try_next() { + self.pending_events.push_back(trigger); + } } - pub fn consume_pending_events(&mut self) {} + // Converts UI events into dirty scopes with various priorities + pub fn consume_pending_events(&mut self) -> Result<()> { + while let Some(trigger) = self.pending_events.pop_back() { + match &trigger.event { + VirtualEvent::AsyncEvent { .. } => {} + + // This suspense system works, but it's not the most elegant solution. + // TODO: Replace this system + VirtualEvent::SuspenseEvent { hook_idx, domnode } => { + todo!("suspense needs to be converted into its own channel"); + + // // Safety: this handler is the only thing that can mutate shared items at this moment in tim + // let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap(); + + // // safety: we are sure that there are no other references to the inner content of suspense hooks + // let hook = unsafe { scope.hooks.get_mut::(*hook_idx) }.unwrap(); + + // let cx = Context { scope, props: &() }; + // let scx = SuspendedContext { inner: cx }; + + // // generate the new node! + // let nodes: Option = (&hook.callback)(scx); + + // if let Some(nodes) = nodes { + // // allocate inside the finished frame - not the WIP frame + // let nodes = scope.frames.finished_frame().bump.alloc(nodes); + + // // push the old node's root onto the stack + // let real_id = domnode.get().ok_or(Error::NotMounted)?; + // diff_machine.edit_push_root(real_id); + + // // push these new nodes onto the diff machines stack + // let meta = diff_machine.create_vnode(&*nodes); + + // // replace the placeholder with the new nodes we just pushed on the stack + // diff_machine.edit_replace_with(1, meta.added_to_stack); + // } else { + // log::warn!( + // "Suspense event came through, but there were no generated nodes >:(." + // ); + // } + } + + VirtualEvent::ClipboardEvent(_) + | VirtualEvent::CompositionEvent(_) + | VirtualEvent::KeyboardEvent(_) + | VirtualEvent::FocusEvent(_) + | VirtualEvent::FormEvent(_) + | VirtualEvent::SelectionEvent(_) + | VirtualEvent::TouchEvent(_) + | VirtualEvent::UIEvent(_) + | VirtualEvent::WheelEvent(_) + | VirtualEvent::MediaEvent(_) + | VirtualEvent::AnimationEvent(_) + | VirtualEvent::TransitionEvent(_) + | VirtualEvent::ToggleEvent(_) + | VirtualEvent::MouseEvent(_) + | VirtualEvent::PointerEvent(_) => { + if let Some(scope) = self.shared.get_scope_mut(trigger.originator) { + if let Some(element) = trigger.real_node_id { + scope.call_listener(trigger.event, element)?; + + let receiver = self.shared.immediate_receiver.clone(); + let mut receiver = receiver.borrow_mut(); + + // Drain the immediates into the dirty scopes, setting the appropiate priorities + while let Ok(Some(dirty_scope)) = receiver.try_next() { + self.add_dirty_scope(dirty_scope, trigger.priority) + } + } + } + } + } + } + + Ok(()) + } // nothing to do, no events on channels, no work - pub fn is_idle(&self) -> bool { - !self.has_work() && self.has_pending_events() + pub fn has_any_work(&self) -> bool { + self.has_work() || self.has_pending_events() || self.has_pending_garbage() } pub fn has_pending_events(&self) -> bool { - todo!() + self.pending_events.len() > 0 } pub fn has_work(&self) -> bool { - todo!() + let has_work = self.high_priorty.has_work() + || self.medium_priority.has_work() + || self.low_priority.has_work(); + !has_work } + pub fn has_pending_garbage(&self) -> bool { + !self.garbage_scopes.is_empty() + } + + /// If a the fiber finishes its works (IE needs to be committed) the scheduler will drop the dirty scope pub fn work_with_deadline( &mut self, - deadline: &mut impl Future, + mut deadline: &mut Pin>>, is_deadline_reached: &mut impl FnMut() -> bool, ) -> FiberResult { + // check if we need to elevate priority + self.current_priority = match ( + self.high_priorty.has_work(), + self.medium_priority.has_work(), + self.low_priority.has_work(), + ) { + (true, _, _) => EventPriority::High, + (false, true, _) => EventPriority::Medium, + (false, false, _) => EventPriority::Low, + }; + + let mut current_fiber = match self.current_priority { + EventPriority::High => &mut self.high_priorty, + EventPriority::Medium => &mut self.medium_priority, + EventPriority::Low => &mut self.low_priority, + }; + todo!() } // waits for a trigger, canceling early if the deadline is reached // returns true if the deadline was reached // does not return the trigger, but caches it in the scheduler - pub async fn wait_for_any_trigger(&mut self, deadline: &mut impl Future) -> bool { - // poll_all_channels() - // if no events and no work { - // wait for events, cache them - // } + pub async fn wait_for_any_trigger( + &mut self, + mut deadline: &mut Pin>>, + ) -> bool { + use futures_util::select; - // for event in pending_evenst { - // mark_dirty_scopes_with_priorities - // } + let _immediates = self.shared.immediate_receiver.clone(); + let mut immediates = _immediates.borrow_mut(); - // // level shift the priority - // match current_priority { - // high -> finish high queue - // medium -> if work in high { set_current_level_high } - // low -> if work in high { set_current_level_high } else if work in medium { set_current_level_medium } - // } + let mut _ui_events = self.shared.ui_event_receiver.clone(); + let mut ui_events = _ui_events.borrow_mut(); - // let mut fiber = load_priority_fiber(); + let mut _tasks = self.shared.async_tasks.clone(); + let mut tasks = _tasks.borrow_mut(); - // fiber.load_work(high_priority_work); + // set_state called + select! { + dirty_scope = immediates.next() => { + if let Some(scope) = dirty_scope { + self.add_dirty_scope(scope, EventPriority::Low); + } + } - // fiber.progress_with_deadline(deadline); + ui_event = ui_events.next() => { + if let Some(event) = ui_event { + self.pending_events.push_back(event); + } + } - // if fiber.finished() { - // if work_pending_for_priority(fiber.priority()) { - // fiber.load_work(priority_work) - // } else { - // shift_level_down() - // } - // } + async_task = tasks.next() => { + if let Some(event) = async_task { + self.pending_events.push_back(event); + } + } - // load current events - // if work queue is empty { - // wait for events - // load current events - // } + _ = deadline => { + return true; + } - todo!() - // match raw_trigger { - // Ok(Some(trigger)) => trigger, - // _ => { - // // nothing to do - let's clean up any garbage we might have - // self.scheduler.clean_up_garbage(); - - // // Continuously poll the future pool and the event receiver for work - // let mut tasks = self.shared.async_tasks.borrow_mut(); - // let tasks_tasks = tasks.next(); - - // // if the new event generates work more important than our current fiber, we should consider switching - // // only switch if it impacts different scopes. - // let mut ui_receiver = self.shared.ui_event_receiver.borrow_mut(); - // let ui_reciv_task = ui_receiver.next(); - - // // right now, this polling method will only catch batched set_states that don't get awaited. - // // However, in the future, we might be interested in batching set_states across await points - // let immediate_tasks = (); - - // futures_util::pin_mut!(tasks_tasks); - // futures_util::pin_mut!(ui_reciv_task); - - // // Poll the event receiver and the future pool for work - // // Abort early if our deadline has ran out - // let mut deadline = (&mut deadline_future).fuse(); - - // let trig = futures_util::select! { - // trigger = tasks_tasks => trigger, - // trigger = ui_reciv_task => trigger, - - // // abort if we're out of time - // _ = deadline => { return Ok(Mutations::new()); } - // // _ = deadline => { return Ok(diff_machine.mutations); } - // }; - - // trig.unwrap() - // } - // }; - // + } + false } pub fn add_dirty_scope(&mut self, scope: ScopeId, priority: EventPriority) { - // - - // generated_immediates - // .entry(dirty_scope) - // .and_modify(|cur_priority| { - // if *cur_priority > new_priority { - // *cur_priority = new_priority; - // } - // }) - // .or_insert_with(|| new_priority); - } - - // returns true if the deadline is reached - // returns false if the deadline is not reached - pub fn progress_work( - &mut self, - machine: &mut DiffMachine, - is_deadline_reached: &mut impl FnMut() -> bool, - ) -> bool { - todo!() + match priority { + EventPriority::High => self.high_priorty.dirty_scopes.insert(scope), + EventPriority::Medium => self.medium_priority.dirty_scopes.insert(scope), + EventPriority::Low => self.low_priority.dirty_scopes.insert(scope), + }; } pub fn clean_up_garbage(&mut self) { let mut scopes_to_kill = Vec::new(); - let mut garbage_list = Vec::new(); + for scope in self.garbage_scopes.drain() { let scope = self.shared.get_scope_mut(scope).unwrap(); for node in scope.consume_garbage() { @@ -278,18 +360,22 @@ impl Scheduler { } } } + + for scope in scopes_to_kill.drain(..) { + // + // kill em + } } } -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] pub struct DirtyScope { height: u32, - priority: EventPriority, start_tick: u32, } // fibers in dioxus aren't exactly the same as React's. Our fibers are more like a "saved state" of the diffing algorithm. -pub struct Fiber<'a> { +pub struct PriorityFiber<'a> { // scopes that haven't been updated yet pending_scopes: Vec, @@ -302,9 +388,15 @@ pub struct Fiber<'a> { // a fiber is finished when no more scopes or nodes are pending completed: bool, + + dirty_scopes: IndexSet, + + wip_edits: Vec>, + + current_batch_scopes: HashSet, } -impl Fiber<'_> { +impl PriorityFiber<'_> { fn new() -> Self { Self { pending_scopes: Vec::new(), @@ -312,153 +404,15 @@ impl Fiber<'_> { edits: Vec::new(), started: false, completed: false, + dirty_scopes: IndexSet::new(), + wip_edits: Vec::new(), + current_batch_scopes: HashSet::new(), } } + + fn has_work(&self) -> bool { + self.dirty_scopes.is_empty() + && self.wip_edits.is_empty() + && self.current_batch_scopes.is_empty() + } } - -/* -Strategy: -1. Check if there are any UI events in the receiver. -2. If there are, run the listener and then mark the dirty nodes -3. If there are dirty nodes to be progressed, do so. -4. Poll the task queue to see if we can create more dirty scopes. -5. Resume any current in-flight work if there is some. -6. While the deadline is not met, progress work, periodically checking the deadline. - - -How to choose work: -- When a scope is marked as dirty, it is given a priority. -- If a dirty scope chains (borrowed) into children, mark those as dirty as well. -- When the work loop starts, work on the highest priority scopes first. -- Work by priority, choosing to pause in-flight work if higher-priority work is ready. - - - -4. If there are no fibers, then wait for the next event from the receiver. Abort if the deadline is reached. -5. While processing a fiber, periodically check if we're out of time -6. If our deadling is reached, then commit our edits to the realdom -7. Whenever a fiber is finished, immediately commit it. (IE so deadlines can be infinite if unsupported) - - -// 1. Check if there are any events in the receiver. -// 2. If there are, process them and create a new fiber. -// 3. If there are no events, then choose a fiber to work on. -// 4. If there are no fibers, then wait for the next event from the receiver. Abort if the deadline is reached. -// 5. While processing a fiber, periodically check if we're out of time -// 6. If our deadling is reached, then commit our edits to the realdom -// 7. Whenever a fiber is finished, immediately commit it. (IE so deadlines can be infinite if unsupported) - -We slice fibers based on time. Each batch of events between frames is its own fiber. This is the simplest way -to conceptualize what *is* or *isn't* a fiber. IE if a bunch of events occur during a time slice, they all -get batched together as a single operation of "dirty" scopes. - -This approach is designed around the "diff during rIC and commit during rAF" - -We need to make sure to not call multiple events while the diff machine is borrowing the same scope. Because props -and listeners hold references to hook data, it is wrong to run a scope that is already being diffed. -*/ - -// // 1. Drain the existing immediates. -// // -// // These are generated by async tasks that we never got a chance to finish. -// // All of these get scheduled with the lowest priority. -// while let Ok(Some(dirty_scope)) = self.shared.immediate_receiver.borrow_mut().try_next() -// { -// self.scheduler -// .add_dirty_scope(dirty_scope, EventPriority::Low); -// } - -// // 2. Drain the event queues, calling whatever listeners need to be called -// // -// // First, check if there's any set_states in the queue that we can mark as low priority -// // Next, check the UI event receiver so we can mark those scopes as medium/high priority -// // Next, work on any fibers. -// // Once the fiber work is finished -// while let Some(trigger) = self.scheduler.next_event() { -// match &trigger.event { -// VirtualEvent::AsyncEvent { .. } => {} - -// // This suspense system works, but it's not the most elegant solution. -// // TODO: Replace this system -// VirtualEvent::SuspenseEvent { hook_idx, domnode } => { -// todo!(); -// // // Safety: this handler is the only thing that can mutate shared items at this moment in tim -// // let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap(); - -// // // safety: we are sure that there are no other references to the inner content of suspense hooks -// // let hook = unsafe { scope.hooks.get_mut::(*hook_idx) }.unwrap(); - -// // let cx = Context { scope, props: &() }; -// // let scx = SuspendedContext { inner: cx }; - -// // // generate the new node! -// // let nodes: Option = (&hook.callback)(scx); - -// // if let Some(nodes) = nodes { -// // // allocate inside the finished frame - not the WIP frame -// // let nodes = scope.frames.finished_frame().bump.alloc(nodes); - -// // // push the old node's root onto the stack -// // let real_id = domnode.get().ok_or(Error::NotMounted)?; -// // diff_machine.edit_push_root(real_id); - -// // // push these new nodes onto the diff machines stack -// // let meta = diff_machine.create_vnode(&*nodes); - -// // // replace the placeholder with the new nodes we just pushed on the stack -// // diff_machine.edit_replace_with(1, meta.added_to_stack); -// // } else { -// // log::warn!( -// // "Suspense event came through, but there were no generated nodes >:(." -// // ); -// // } -// } - -// VirtualEvent::ClipboardEvent(_) -// | VirtualEvent::CompositionEvent(_) -// | VirtualEvent::KeyboardEvent(_) -// | VirtualEvent::FocusEvent(_) -// | VirtualEvent::FormEvent(_) -// | VirtualEvent::SelectionEvent(_) -// | VirtualEvent::TouchEvent(_) -// | VirtualEvent::UIEvent(_) -// | VirtualEvent::WheelEvent(_) -// | VirtualEvent::MediaEvent(_) -// | VirtualEvent::AnimationEvent(_) -// | VirtualEvent::TransitionEvent(_) -// | VirtualEvent::ToggleEvent(_) -// | VirtualEvent::MouseEvent(_) -// | VirtualEvent::PointerEvent(_) => { -// if let Some(scope) = self.shared.get_scope_mut(trigger.originator) { -// if let Some(element) = trigger.real_node_id { -// scope.call_listener(trigger.event, element)?; - -// // Drain the immediates into the dirty scopes, setting the appropiate priorities -// while let Ok(Some(dirty_scope)) = -// self.shared.immediate_receiver.borrow_mut().try_next() -// { -// self.scheduler -// .add_dirty_scope(dirty_scope, trigger.priority) -// } -// } -// } -// } -// } -// } - -// // todo: for these, make these methods return results where deadlinereached is an error type - -// // 3. Work through the fibers, and wait for any future work to be ready -// let mut machine = DiffMachine::new_headless(&self.shared); -// if self.scheduler.progress_work(&mut machine, &mut is_ready) { -// break; -// } - -// // 4. Wait for any new triggers before looping again -// if self -// .scheduler -// .wait_for_any_trigger(&mut deadline_future) -// .await -// { -// break; -// } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 23141c127..0dafc2ea2 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -19,7 +19,7 @@ //! This module includes just the barebones for a complete VirtualDOM API. //! Additional functionality is defined in the respective files. use crate::innerlude::*; -use futures_util::Future; +use futures_util::{pin_mut, Future, FutureExt}; use std::{ any::{Any, TypeId}, pin::Pin, @@ -152,8 +152,7 @@ impl VirtualDom { /// Events like garabge collection, application of refs, etc are not handled by this method and can only be progressed /// through "run" /// - pub fn rebuild<'s>(&'s mut self) -> Result>> { - let mut edits = Vec::new(); + pub fn rebuild<'s>(&'s mut self) -> Result> { let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.shared); let cur_component = diff_machine @@ -172,7 +171,7 @@ impl VirtualDom { ); } - Ok(edits) + Ok(diff_machine.mutations) } /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete. @@ -258,33 +257,53 @@ impl VirtualDom { /// queue is completely drained; async fn run_with_deadline_and_is_ready<'s>( &'s mut self, - mut deadline: impl Future, + deadline: impl Future, is_ready: &mut impl FnMut() -> bool, ) -> Result> { - let mutations = Mutations::new(); + let mut committed_mutations = Mutations::new(); + let mut deadline = Box::pin(deadline.fuse()); + // TODO: + // the scheduler uses a bunch of different receivers to mimic a "topic" queue system. The futures-channel implementation + // doesn't really have a concept of a "topic" queue, so there's a lot of noise in the hand-rolled scheduler. We should + // explore abstracting the scheduler into a topic-queue channel system - similar to Kafka or something similar. loop { - self.scheduler.manually_poll_channels(); + // Internalize any pending work since the last time we ran + self.scheduler.manually_poll_events(); - // essentially, is idle - if self.scheduler.is_idle() { + // Wait for any new events if we have nothing to do + if !self.scheduler.has_any_work() { + self.scheduler.clean_up_garbage(); let deadline_expired = self.scheduler.wait_for_any_trigger(&mut deadline).await; if deadline_expired { - return Ok(mutations); + return Ok(committed_mutations); } } - self.scheduler.consume_pending_events(); + // Create work from the pending event queue + self.scheduler.consume_pending_events()?; + // Work through the current subtree, and commit the results when it finishes + // When the deadline expires, give back the work match self.scheduler.work_with_deadline(&mut deadline, is_ready) { - FiberResult::Done(mutations) => { - // commit these mutations - } - FiberResult::Interrupted => { - // - return Ok(mutations); + FiberResult::Done(mut mutations) => { + committed_mutations.extend(&mut mutations); + + /* + quick return if there's no work left, so we can commit before the deadline expires + When we loop over again, we'll re-wait for any new work. + + I'm not quite sure how this *should* work. + + It makes sense to try and progress the DOM faster + */ + + if !self.scheduler.has_any_work() { + return Ok(committed_mutations); + } } + FiberResult::Interrupted => return Ok(committed_mutations), } } } diff --git a/packages/core/tests/hooks.rs b/packages/core/tests/hooks.rs index 6e3941e37..2c434eabe 100644 --- a/packages/core/tests/hooks.rs +++ b/packages/core/tests/hooks.rs @@ -10,19 +10,17 @@ use dioxus_html as dioxus_elements; #[test] fn sample_refs() { - static App: FC<()> = |cx| { - let div_ref = use_node_ref::(cx); + // static App: FC<()> = |cx| { + // let div_ref = use_node_ref::(cx); - cx.render(rsx! { - div { - style: { color: "red" } - node_ref: div_ref - onmouseover: move |_| { - div_ref.borrow_mut().focus(); - } - } - }) - }; + // cx.render(rsx! { + // div { + // style: { color: "red" }, + // node_ref: div_ref, + // onmouseover: move |_| { + // div_ref.borrow_mut().focus(); + // }, + // }, + // }) + // }; } - -struct MyRef {} diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index 54d1676c8..a3c1fdb3b 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/packages/core/tests/vdom_rebuild.rs @@ -19,7 +19,7 @@ fn app_runs() { cx.render(rsx!( div{"hello"} )) }; let mut vdom = VirtualDom::new(App); - let edits = vdom.rebuild_in_place().unwrap(); + let edits = vdom.rebuild().unwrap(); dbg!(edits); } @@ -32,7 +32,7 @@ fn fragments_work() { )) }; let mut vdom = VirtualDom::new(App); - let edits = vdom.rebuild_in_place().unwrap(); + let edits = vdom.rebuild().unwrap(); // should result in a final "appendchildren n=2" dbg!(edits); } @@ -46,7 +46,7 @@ fn lists_work() { )) }; let mut vdom = VirtualDom::new(App); - let edits = vdom.rebuild_in_place().unwrap(); + let edits = vdom.rebuild().unwrap(); dbg!(edits); } @@ -61,10 +61,10 @@ fn conditional_rendering() { }; let mut vdom = VirtualDom::new(App); - let edits = vdom.rebuild_in_place().unwrap(); - dbg!(&edits); + let mutations = vdom.rebuild().unwrap(); + dbg!(&mutations); // the "false" fragment should generate an empty placeholder to re-visit - assert!(edits[edits.len() - 2].is("CreatePlaceholder")); + assert!(mutations.edits[mutations.edits.len() - 2].is("CreatePlaceholder")); } #[test] @@ -82,7 +82,7 @@ fn child_components() { )) }; let mut vdom = VirtualDom::new(App); - let edits = vdom.rebuild_in_place().unwrap(); + let edits = vdom.rebuild().unwrap(); dbg!(edits); } @@ -94,6 +94,6 @@ fn suspended_works() { }; let mut vdom = VirtualDom::new(App); - let edits = vdom.rebuild_in_place().unwrap(); + let edits = vdom.rebuild().unwrap(); dbg!(edits); }