From fd0c384daccf89cec408e3ad674ea04c00797eae Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 7 Nov 2021 00:06:00 -0400 Subject: [PATCH] wip: it compiles cleanly --- packages/core/src/diff.rs | 80 ++------ packages/core/src/scopearena.rs | 19 +- packages/core/src/virtual_dom.rs | 303 +++++++++++++------------------ 3 files changed, 158 insertions(+), 244 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index a1a0cfe6a..46e278ca7 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -108,7 +108,7 @@ pub struct DiffState<'bump> { pub mutations: Mutations<'bump>, pub(crate) stack: DiffStack<'bump>, pub seen_scopes: FxHashSet, - pub(crate) cfg: DiffCfg, + pub force_diff: bool, } impl<'bump> DiffState<'bump> { @@ -117,68 +117,14 @@ impl<'bump> DiffState<'bump> { mutations, stack: DiffStack::new(), seen_scopes: Default::default(), - cfg: Default::default(), + force_diff: false, } } } -pub(crate) struct DiffCfg { - pub force_diff: bool, -} -impl Default for DiffCfg { - fn default() -> Self { - Self { - force_diff: Default::default(), - } - } -} - -/// a "saved" form of a diff machine -/// in regular diff machine, the &'bump reference is a stack borrow, but the -/// bump lifetimes are heap borrows. -pub(crate) struct SavedDiffWork<'bump> { - pub mutations: Mutations<'bump>, - pub stack: DiffStack<'bump>, - pub seen_scopes: FxHashSet, -} - -impl<'a> SavedDiffWork<'a> { - pub unsafe fn extend(self: SavedDiffWork<'a>) -> SavedDiffWork<'static> { - std::mem::transmute(self) - } - - pub unsafe fn promote<'b>(self) -> DiffState<'b> { - let extended: SavedDiffWork<'b> = std::mem::transmute(self); - DiffState { - cfg: DiffCfg::default(), - mutations: extended.mutations, - stack: extended.stack, - seen_scopes: extended.seen_scopes, - } - } -} - -impl<'bump> VirtualDom { - // impl<'bump> DiffState<'bump> { - // pub(crate) fn new(mutations: Mutations<'bump>) -> Self { - // Self { - // mutations, - // cfg: DiffCfg::default(), - // stack: DiffStack::new(), - // seen_scopes: FxHashSet::default(), - // } - // } - - // pub fn save(self) -> SavedDiffWork<'bump> { - // SavedDiffWork { - // mutations: state.mutations, - // stack: state.stack, - // seen_scopes: self.seen_scopes, - // } - // } - - pub fn diff_scope(&'bump self, state: &mut DiffState<'bump>, id: ScopeId) { - if let Some(component) = self.scopes.get(&id) { +impl<'bump> ScopeArena { + pub fn diff_scope(&'bump self, state: &mut DiffState<'bump>, id: &ScopeId) { + if let Some(component) = self.get_scope(&id) { let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); state.stack.push(DiffInstruction::Diff { new, old }); self.work(state, || false); @@ -382,7 +328,7 @@ impl<'bump> VirtualDom { state.stack.add_child_count(1); if let Some(cur_scope_id) = state.stack.current_scope() { - let scope = self.scopes.get(&cur_scope_id).unwrap(); + let scope = self.get_scope(&cur_scope_id).unwrap(); for listener in *listeners { self.attach_listener_to_scope(state, listener, scope); @@ -427,7 +373,7 @@ impl<'bump> VirtualDom { let parent_scope = self.get_scope(&parent_idx).unwrap(); let new_idx: ScopeId = todo!(); - // self.scopes + // self // .new_with_key(fc_ptr, vcomp, parent_scope, height, subtree, sender); // .(|new_idx| { @@ -445,7 +391,7 @@ impl<'bump> VirtualDom { vcomponent.associated_scope.set(Some(new_idx)); if !vcomponent.can_memoize { - let cur_scope = self.scopes.get(&parent_idx).unwrap(); + let cur_scope = self.get_scope(&parent_idx).unwrap(); let extended = vcomponent as *const VComponent; let extended: *const VComponent<'static> = unsafe { std::mem::transmute(extended) }; @@ -456,7 +402,7 @@ impl<'bump> VirtualDom { // add noderefs to current noderef list Noderefs // add effects to current effect list Effects - let new_component = self.scopes.get(&new_idx).unwrap(); + let new_component = self.get_scope(&new_idx).unwrap(); log::debug!( "initializing component {:?} with height {:?}", @@ -606,7 +552,7 @@ impl<'bump> VirtualDom { // // TODO: take a more efficient path than this if let Some(cur_scope_id) = state.stack.current_scope() { - let scope = self.scopes.get(&cur_scope_id).unwrap(); + let scope = self.get_scope(&cur_scope_id).unwrap(); if old.listeners.len() == new.listeners.len() { for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { @@ -664,7 +610,7 @@ impl<'bump> VirtualDom { new.associated_scope.set(Some(scope_addr)); // make sure the component's caller function is up to date - let scope = self.scopes.get(&scope_addr).unwrap(); + let scope = self.get_scope(&scope_addr).unwrap(); scope.update_vcomp(new); // React doesn't automatically memoize, but we do. @@ -1312,7 +1258,7 @@ impl<'bump> VirtualDom { VNode::Component(c) => { let scope_id = c.associated_scope.get().unwrap(); - let scope = self.scopes.get(&scope_id).unwrap(); + let scope = self.get_scope(&scope_id).unwrap(); let root = scope.root_node(); self.remove_nodes(state, Some(root), gen_muts); @@ -1366,7 +1312,7 @@ impl<'bump> VirtualDom { if let Some(scope) = state .stack .current_scope() - .and_then(|id| self.scopes.get(&id)) + .and_then(|id| self.get_scope(&id)) { // safety: this lifetime is managed by the logic on scope let extended: &VSuspended<'static> = unsafe { std::mem::transmute(suspended) }; diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index 4e5b2e096..5a7011f3c 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -21,18 +21,20 @@ pub(crate) struct ScopeArena { bump: Bump, scopes: Vec<*mut ScopeState>, free_scopes: Vec, + pub(crate) sender: UnboundedSender, } impl ScopeArena { - pub fn new() -> Self { + pub fn new(sender: UnboundedSender) -> Self { Self { bump: Bump::new(), scopes: Vec::new(), free_scopes: Vec::new(), + sender, } } - pub fn get(&self, id: &ScopeId) -> Option<&ScopeState> { + pub fn get_scope(&self, id: &ScopeId) -> Option<&ScopeState> { unsafe { Some(&*self.scopes[id.0]) } } @@ -86,5 +88,18 @@ impl ScopeArena { } } + pub fn try_remove(&self, id: &ScopeId) -> Option { + todo!() + } + + pub fn reserve_node(&self, node: &VNode) -> ElementId { + todo!() + // self.node_reservations.insert(id); + } + + pub fn collect_garbage(&self, id: ElementId) { + todo!() + } + // scopes never get dropepd } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index f61ae96c8..f25922d7b 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -88,8 +88,6 @@ pub struct VirtualDom { pending_messages: VecDeque, dirty_scopes: IndexSet, - saved_state: Option>, - in_progress: bool, } @@ -164,7 +162,7 @@ impl VirtualDom { sender: UnboundedSender, receiver: UnboundedReceiver, ) -> Self { - let mut scopes = ScopeArena::new(); + let mut scopes = ScopeArena::new(sender.clone()); let base_scope = scopes.new_with_key( // @@ -191,7 +189,6 @@ impl VirtualDom { pending_futures: Default::default(), dirty_scopes: Default::default(), - saved_state: None, in_progress: false, } } @@ -216,7 +213,7 @@ impl VirtualDom { /// /// pub fn get_scope<'a>(&'a self, id: &ScopeId) -> Option<&'a ScopeState> { - self.scopes.get(&id) + self.scopes.get_scope(&id) } /// Get an [`UnboundedSender`] handle to the channel used by the scheduler. @@ -246,62 +243,73 @@ impl VirtualDom { /// This lets us poll async tasks during idle periods without blocking the main thread. pub async fn wait_for_work(&mut self) { // todo: poll the events once even if there is work to do to prevent starvation - if self.has_any_work() { - return; - } - struct PollTasks<'a> { - pending_futures: &'a FxHashSet, - scopes: &'a ScopeArena, - } + // if there's no futures in the virtualdom, just wait for a scheduler message and put it into the queue to be processed + if self.pending_futures.is_empty() { + self.pending_messages + .push_front(self.receiver.next().await.unwrap()); + } else { + struct PollTasks<'a> { + pending_futures: &'a FxHashSet, + scopes: &'a ScopeArena, + } - impl<'a> Future for PollTasks<'a> { - type Output = (); + impl<'a> Future for PollTasks<'a> { + type Output = (); - fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - let mut all_pending = true; + fn poll( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll { + let mut all_pending = true; - // Poll every scope manually - for fut in self.pending_futures.iter() { - let scope = self.scopes.get(fut).expect("Scope should never be moved"); + // Poll every scope manually + for fut in self.pending_futures.iter() { + let scope = self + .scopes + .get_scope(fut) + .expect("Scope should never be moved"); - let mut items = scope.items.borrow_mut(); - for task in items.tasks.iter_mut() { - let task = task.as_mut(); + let mut items = scope.items.borrow_mut(); + for task in items.tasks.iter_mut() { + let task = task.as_mut(); - // todo: does this make sense? - // I don't usually write futures by hand - let unpinned = unsafe { Pin::new_unchecked(task) }; - match unpinned.poll(cx) { - Poll::Ready(_) => { - all_pending = false; + // todo: does this make sense? + // I don't usually write futures by hand + // I think the futures neeed to be pinned using bumpbox or something + // right now, they're bump allocated so this shouldn't matter anyway - they're not going to move + let unpinned = unsafe { Pin::new_unchecked(task) }; + + if let Poll::Ready(_) = unpinned.poll(cx) { + all_pending = false } - Poll::Pending => {} } } - } - // Resolve the future if any task is ready - match all_pending { - true => Poll::Pending, - false => Poll::Ready(()), + // Resolve the future if any singular task is ready + match all_pending { + true => Poll::Pending, + false => Poll::Ready(()), + } } } - } - let scheduler_fut = self.receiver.next(); - let tasks_fut = PollTasks { - pending_futures: &self.pending_futures, - scopes: &self.scopes, - }; + // Poll both the futures and the scheduler message queue simulataneously + use futures_util::future::{select, Either}; - use futures_util::future::{select, Either}; - match select(tasks_fut, scheduler_fut).await { - // Tasks themselves don't generate work - Either::Left((_, _)) => {} + let scheduler_fut = self.receiver.next(); + let tasks_fut = PollTasks { + pending_futures: &self.pending_futures, + scopes: &self.scopes, + }; - // Save these messages in FIFO to be processed later - Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()), + match select(tasks_fut, scheduler_fut).await { + // Futures don't generate work + Either::Left((_, _)) => {} + + // Save these messages in FIFO to be processed later + Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()), + } } } @@ -333,10 +341,15 @@ impl VirtualDom { /// /// ```no_run /// static App: FC<()> = |(cx, props)|rsx!(cx, div {"hello"} ); + /// /// let mut dom = VirtualDom::new(App); + /// /// loop { - /// let deadline = TimeoutFuture::from_ms(16); + /// let mut timeout = TimeoutFuture::from_ms(16); + /// let deadline = move || timeout.now_or_never(); + /// /// let mutations = dom.run_with_deadline(deadline).await; + /// /// apply_mutations(mutations); /// } /// ``` @@ -352,124 +365,91 @@ impl VirtualDom { &'a mut self, mut deadline: impl FnMut() -> bool, ) -> Vec> { - let mut committed_mutations = Vec::>::new(); + let mut committed_mutations = Vec::::new(); while self.has_any_work() { while let Ok(Some(msg)) = self.receiver.try_next() { self.pending_messages.push_front(msg); } - for msg in self.pending_messages.drain(..) { + while let Some(msg) = self.pending_messages.pop_back() { match msg { SchedulerMsg::Immediate(id) => { - // it's dirty self.dirty_scopes.insert(id); } SchedulerMsg::UiEvent(event) => { - // there's an event - let scope = self.scopes.get(&event.scope_id).unwrap(); if let Some(element) = event.mounted_dom_id { log::info!("Calling listener {:?}, {:?}", event.scope_id, element); + let scope = self.scopes.get_scope(&event.scope_id).unwrap(); + // TODO: bubble properly here scope.call_listener(event, element); while let Ok(Some(dirty_scope)) = self.receiver.try_next() { - match dirty_scope { - SchedulerMsg::Immediate(im) => { - self.dirty_scopes.insert(im); - } - SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e), - } + self.pending_messages.push_front(dirty_scope); } + } else { + log::debug!("User event without a targetted ElementId. Unsure how to proceed. {:?}", event); } } } } - let work_complete = { - // Work through the current subtree, and commit the results when it finishes - // When the deadline expires, give back the work - let saved_state = unsafe { self.load_work() }; + let mut diff_state: DiffState = DiffState::new(Mutations::new()); - // We have to split away some parts of ourself - current lane is borrowed mutably - let mut machine = unsafe { saved_state.promote() }; + let mut ran_scopes = FxHashSet::default(); - let mut ran_scopes = FxHashSet::default(); + // todo: the 2021 version of rust will let us not have to force the borrow + let scopes = &self.scopes; - if machine.stack.is_empty() { - todo!("order scopes"); - // self.dirty_scopes.retain(|id| self.get_scope(id).is_some()); - // self.dirty_scopes.sort_by(|a, b| { - // let h1 = self.get_scope(a).unwrap().height; - // let h2 = self.get_scope(b).unwrap().height; - // h1.cmp(&h2).reverse() - // }); + // Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height + self.dirty_scopes + .retain(|id| scopes.get_scope(id).is_some()); - if let Some(scopeid) = self.dirty_scopes.pop() { - log::info!("handling dirty scope {:?}", scopeid); - if !ran_scopes.contains(&scopeid) { - ran_scopes.insert(scopeid); - log::debug!("about to run scope {:?}", scopeid); + self.dirty_scopes.sort_by(|a, b| { + let h1 = scopes.get_scope(a).unwrap().height; + let h2 = scopes.get_scope(b).unwrap().height; + h1.cmp(&h2).reverse() + }); - // if let Some(component) = self.get_scope_mut(&scopeid) { - if self.run_scope(&scopeid) { - todo!("diff the scope") - // let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); - // // let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); - // machine.stack.scope_stack.push(scopeid); - // machine.stack.push(DiffInstruction::Diff { new, old }); - } - // } - } + if let Some(scopeid) = self.dirty_scopes.pop() { + log::info!("handling dirty scope {:?}", scopeid); + + if !ran_scopes.contains(&scopeid) { + ran_scopes.insert(scopeid); + + log::debug!("about to run scope {:?}", scopeid); + + if self.run_scope(&scopeid) { + let scope = self.scopes.get_scope(&scopeid).unwrap(); + let (old, new) = (scope.frames.wip_head(), scope.frames.fin_head()); + diff_state.stack.scope_stack.push(scopeid); + diff_state.stack.push(DiffInstruction::Diff { new, old }); } } + } - let work_completed: bool = todo!(); - // let work_completed = machine.work(deadline_reached); + let work_completed = self.scopes.work(&mut diff_state, &mut deadline); - // log::debug!("raw edits {:?}", machine.mutations.edits); + if work_completed { + let DiffState { + mutations, + seen_scopes, + stack, + .. + } = diff_state; - let mut machine: DiffState<'static> = unsafe { std::mem::transmute(machine) }; - // let mut saved = machine.save(); - - if work_completed { - for node in machine.seen_scopes.drain() { - // self.dirty_scopes.clear(); - // self.ui_events.clear(); - self.dirty_scopes.remove(&node); - // self.dirty_scopes.remove(&node); - } - - let mut new_mutations = Mutations::new(); - - for edit in machine.mutations.edits.drain(..) { - new_mutations.edits.push(edit); - } - - // for edit in saved.edits.drain(..) { - // new_mutations.edits.push(edit); - // } - - // std::mem::swap(&mut new_mutations, &mut saved.mutations); - - mutations.push(new_mutations); - - // log::debug!("saved edits {:?}", mutations); - - todo!(); - // let mut saved = machine.save(); - // self.save_work(saved); - true - - // self.save_work(saved); - // false - } else { - false + for scope in seen_scopes { + self.dirty_scopes.remove(&scope); } - }; - if !work_complete { + // I think the stack should be empty at the end of diffing? + debug_assert_eq!(stack.scope_stack.len(), 0); + + committed_mutations.push(mutations); + } else { + todo!("don't have a mechanism to pause work (yet)"); return committed_mutations; } } @@ -495,7 +475,13 @@ impl VirtualDom { /// apply_edits(edits); /// ``` pub fn rebuild(&mut self) -> Mutations { - self.hard_diff(&self.base_scope) + // todo: I think we need to append a node or something + // diff_machine + // .stack + // .create_node(cur_component.frames.fin_head(), MountType::Append); + + let scope = self.base_scope; + self.hard_diff(&scope).unwrap() } /// Compute a manual diff of the VirtualDOM between states. @@ -532,59 +518,26 @@ impl VirtualDom { /// /// let edits = dom.diff(); /// ``` - pub fn hard_diff<'a>(&'a mut self, base_scope: &ScopeId) -> Mutations<'a> { - // // TODO: drain any in-flight work - // // We run the component. If it succeeds, then we can diff it and add the changes to the dom. - // if self.run_scope(&base_scope) { - // let cur_component = self - // .get_scope_mut(&base_scope) - // .expect("The base scope should never be moved"); + pub fn hard_diff<'a>(&'a mut self, scope_id: &ScopeId) -> Option> { + log::debug!("hard diff {:?}", scope_id); - // log::debug!("rebuild {:?}", base_scope); - - // let mut diff_machine = DiffState::new(Mutations::new()); - // diff_machine - // .stack - // .create_node(cur_component.frames.fin_head(), MountType::Append); - - // diff_machine.stack.scope_stack.push(base_scope); - - // todo!() - // // self.work(&mut diff_machine, || false); - // // diff_machine.work(|| false); - // } else { - // // todo: should this be a hard error? - // log::warn!( - // "Component failed to run successfully during rebuild. - // This does not result in a failed rebuild, but indicates a logic failure within your app." - // ); - // } - - // todo!() - // // unsafe { std::mem::transmute(diff_machine.mutations) } - - let cur_component = self - .scopes - .get(&base_scope) - .expect("The base scope should never be moved"); - - log::debug!("hard diff {:?}", base_scope); - - if self.run_scope(&base_scope) { + if self.run_scope(&scope_id) { let mut diff_machine = DiffState::new(Mutations::new()); - diff_machine.cfg.force_diff = true; - self.diff_scope(&mut diff_machine, base_scope); - // diff_machine.diff_scope(base_scope); - diff_machine.mutations + + diff_machine.force_diff = true; + + self.scopes.diff_scope(&mut diff_machine, scope_id); + + Some(diff_machine.mutations) } else { - Mutations::new() + None } } - pub fn run_scope(&mut self, id: &ScopeId) -> bool { + pub fn run_scope(&self, id: &ScopeId) -> bool { let scope = self .scopes - .get(id) + .get_scope(id) .expect("The base scope should never be moved"); // Cycle to the next frame and then reset it