From 9bad802beb9e99f28a461b18391d502fd9cef6cd Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 7 Mar 2024 21:04:48 -0600 Subject: [PATCH] clean up dirty scopes module --- packages/core/src/dirty_scope.rs | 77 +++++++++++------- packages/core/src/virtual_dom.rs | 79 ++++++++----------- .../examples/static-hydrated/src/main.rs | 2 +- 3 files changed, 79 insertions(+), 79 deletions(-) diff --git a/packages/core/src/dirty_scope.rs b/packages/core/src/dirty_scope.rs index 6e2d71192..4cfb187ef 100644 --- a/packages/core/src/dirty_scope.rs +++ b/packages/core/src/dirty_scope.rs @@ -29,9 +29,9 @@ use crate::ScopeId; use crate::Task; +use crate::VirtualDom; use std::borrow::Borrow; use std::cell::RefCell; -use std::collections::BTreeSet; use std::hash::Hash; #[derive(Debug, Clone, Copy, Eq)] @@ -70,50 +70,71 @@ impl Hash for ScopeOrder { } } -#[derive(Debug, Default)] -pub struct DirtyScopes { - pub(crate) scopes: BTreeSet, - pub(crate) tasks: BTreeSet, -} - -impl DirtyScopes { +impl VirtualDom { /// Queue a task to be polled - pub fn queue_task(&mut self, task: Task, order: ScopeOrder) { - match self.tasks.get(&order) { + pub(crate) fn queue_task(&mut self, task: Task, order: ScopeOrder) { + match self.dirty_tasks.get(&order) { Some(scope) => scope.queue_task(task), None => { let scope = DirtyTasks::from(order); scope.queue_task(task); - self.tasks.insert(scope); + self.dirty_tasks.insert(scope); } } } /// Queue a scope to be rerendered - pub fn queue_scope(&mut self, order: ScopeOrder) { - self.scopes.insert(order); + pub(crate) fn queue_scope(&mut self, order: ScopeOrder) { + self.dirty_scopes.insert(order); } /// Check if there are any dirty scopes - pub fn has_dirty_scopes(&self) -> bool { - !self.scopes.is_empty() + pub(crate) fn has_dirty_scopes(&self) -> bool { + !self.dirty_scopes.is_empty() } /// Take any tasks from the highest scope - pub fn pop_task(&mut self) -> Option { - self.tasks.pop_first() + pub(crate) fn pop_task(&mut self) -> Option { + let mut task = self.dirty_tasks.pop_first()?; + + // If the scope doesn't exist for whatever reason, then we should skip it + while !self.scopes.contains(task.order.id.0) { + task = self.dirty_tasks.pop_first()?; + } + + Some(task) } /// Take any work from the highest scope. This may include rerunning the scope and/or running tasks - pub fn pop_work(&mut self) -> Option { - let dirty_scope = self.scopes.first(); - let dirty_task = self.tasks.first(); + pub(crate) fn pop_work(&mut self) -> Option { + let mut dirty_scope = self.dirty_scopes.first(); + // Pop any invalid scopes off of each dirty task; + while let Some(scope) = dirty_scope { + if !self.scopes.contains(scope.id.0) { + self.dirty_scopes.pop_first(); + dirty_scope = self.dirty_scopes.first(); + } else { + break; + } + } + + let mut dirty_task = self.dirty_tasks.first(); + // Pop any invalid tasks off of each dirty scope; + while let Some(task) = dirty_task { + if !self.scopes.contains(task.order.id.0) { + self.dirty_tasks.pop_first(); + dirty_task = self.dirty_tasks.first(); + } else { + break; + } + } + match (dirty_scope, dirty_task) { (Some(scope), Some(task)) => { let tasks_order = task.borrow(); match scope.cmp(tasks_order) { std::cmp::Ordering::Less => { - let scope = self.scopes.pop_first().unwrap(); + let scope = self.dirty_scopes.pop_first().unwrap(); Some(Work { scope, rerun_scope: true, @@ -121,7 +142,7 @@ impl DirtyScopes { }) } std::cmp::Ordering::Greater => { - let task = self.tasks.pop_first().unwrap(); + let task = self.dirty_tasks.pop_first().unwrap(); Some(Work { scope: task.order, rerun_scope: false, @@ -129,8 +150,8 @@ impl DirtyScopes { }) } std::cmp::Ordering::Equal => { - let scope = self.scopes.pop_first().unwrap(); - let task = self.tasks.pop_first().unwrap(); + let scope = self.dirty_scopes.pop_first().unwrap(); + let task = self.dirty_tasks.pop_first().unwrap(); Some(Work { scope, rerun_scope: true, @@ -140,7 +161,7 @@ impl DirtyScopes { } } (Some(_), None) => { - let scope = self.scopes.pop_first().unwrap(); + let scope = self.dirty_scopes.pop_first().unwrap(); Some(Work { scope, rerun_scope: true, @@ -148,7 +169,7 @@ impl DirtyScopes { }) } (None, Some(_)) => { - let task = self.tasks.pop_first().unwrap(); + let task = self.dirty_tasks.pop_first().unwrap(); Some(Work { scope: task.order, rerun_scope: false, @@ -158,10 +179,6 @@ impl DirtyScopes { (None, None) => None, } } - - pub fn remove(&mut self, scope: &ScopeOrder) { - self.scopes.remove(scope); - } } #[derive(Debug)] diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index fc86b70bc..28a237204 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -2,14 +2,14 @@ //! //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust. -use crate::innerlude::ScopeOrder; +use crate::innerlude::{DirtyTasks, ScopeOrder}; use crate::Task; use crate::{ any_props::AnyProps, arena::ElementId, innerlude::{ - DirtyScopes, ElementRef, ErrorBoundary, NoOpMutations, SchedulerMsg, ScopeState, - VNodeMount, VProps, WriteMutations, + ElementRef, ErrorBoundary, NoOpMutations, SchedulerMsg, ScopeState, VNodeMount, VProps, + WriteMutations, }, nodes::RenderReturn, nodes::{Template, TemplateId}, @@ -20,6 +20,7 @@ use crate::{ use futures_util::StreamExt; use rustc_hash::FxHashMap; use slab::Slab; +use std::collections::BTreeSet; use std::{any::Any, rc::Rc}; use tracing::instrument; @@ -185,7 +186,8 @@ use tracing::instrument; pub struct VirtualDom { pub(crate) scopes: Slab, - pub(crate) dirty_scopes: DirtyScopes, + pub(crate) dirty_scopes: BTreeSet, + pub(crate) dirty_tasks: BTreeSet, // Maps a template path to a map of byte indexes to templates pub(crate) templates: FxHashMap>, @@ -312,6 +314,7 @@ impl VirtualDom { runtime: Runtime::new(tx), scopes: Default::default(), dirty_scopes: Default::default(), + dirty_tasks: Default::default(), templates: Default::default(), queued_templates: Default::default(), elements: Default::default(), @@ -376,7 +379,8 @@ impl VirtualDom { tracing::event!(tracing::Level::TRACE, "Marking scope {:?} as dirty", id); let order = ScopeOrder::new(scope.height(), id); - self.dirty_scopes.queue_scope(order); + drop(scope); + self.queue_scope(order); } /// Mark a task as dirty @@ -396,7 +400,8 @@ impl VirtualDom { ); let order = ScopeOrder::new(scope.height(), scope.id); - self.dirty_scopes.queue_task(task, order); + drop(scope); + self.queue_task(task, order); } /// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an element with a listener, not a static node or a text node.** @@ -450,7 +455,7 @@ impl VirtualDom { self.process_events(); // Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures - if self.dirty_scopes.has_dirty_scopes() { + if self.has_dirty_scopes() { return; } @@ -491,7 +496,7 @@ impl VirtualDom { self.queue_events(); // Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures - if self.dirty_scopes.has_dirty_scopes() { + if self.has_dirty_scopes() { return; } @@ -505,19 +510,14 @@ impl VirtualDom { let _runtime = RuntimeGuard::new(self.runtime.clone()); // Next, run any queued tasks // We choose not to poll the deadline since we complete pretty quickly anyways - while let Some(task) = self.dirty_scopes.pop_task() { - // If the scope doesn't exist for whatever reason, then we should skip it - if !self.scopes.contains(task.order.id.0) { - continue; - } - + while let Some(task) = self.pop_task() { // Then poll any tasks that might be pending let tasks = task.tasks_queued.into_inner(); for task in tasks { let _ = self.runtime.handle_task_wakeup(task); // Running that task, may mark a scope higher up as dirty. If it does, return from the function early self.queue_events(); - if self.dirty_scopes.has_dirty_scopes() { + if self.has_dirty_scopes() { return; } } @@ -609,12 +609,7 @@ impl VirtualDom { // Next, diff any dirty scopes // We choose not to poll the deadline since we complete pretty quickly anyways - while let Some(work) = self.dirty_scopes.pop_work() { - // If the scope doesn't exist for whatever reason, then we should skip it - if !self.scopes.contains(work.scope.id.0) { - continue; - } - + while let Some(work) = self.pop_work() { { let _runtime = RuntimeGuard::new(self.runtime.clone()); // Then, poll any tasks that might be pending in the scope @@ -660,7 +655,7 @@ impl VirtualDom { self.queue_events(); // Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures - if self.dirty_scopes.has_dirty_scopes() { + if self.has_dirty_scopes() { break; } @@ -669,12 +664,7 @@ impl VirtualDom { let _runtime = RuntimeGuard::new(self.runtime.clone()); // Next, run any queued tasks // We choose not to poll the deadline since we complete pretty quickly anyways - while let Some(task) = self.dirty_scopes.pop_task() { - // If the scope doesn't exist for whatever reason, then we should skip it - if !self.scopes.contains(task.order.id.0) { - continue; - } - + while let Some(task) = self.pop_task() { // Then poll any tasks that might be pending let tasks = task.tasks_queued.into_inner(); for task in tasks { @@ -682,7 +672,7 @@ impl VirtualDom { let _ = self.runtime.handle_task_wakeup(task); // Running that task, may mark a scope higher up as dirty. If it does, return from the function early self.queue_events(); - if self.dirty_scopes.has_dirty_scopes() { + if self.has_dirty_scopes() { break 'wait_for_work; } } @@ -694,27 +684,20 @@ impl VirtualDom { } // Render whatever work needs to be rendered, unlocking new futures and suspense leaves - while let Some(work) = self.dirty_scopes.pop_work() { - // If the scope doesn't exist for whatever reason, then we should skip it - if !self.scopes.contains(work.scope.id.0) { - continue; + let _runtime = RuntimeGuard::new(self.runtime.clone()); + while let Some(work) = self.pop_work() { + // Then, poll any tasks that might be pending in the scope + for task in work.tasks { + // During suspense, we only want to run tasks that are suspended + if self.runtime.suspended_tasks.borrow().contains(&task) { + let _ = self.runtime.handle_task_wakeup(task); + } } + // If the scope is dirty, run the scope and get the mutations + if work.rerun_scope { + let new_nodes = self.run_scope(work.scope.id); - { - let _runtime = RuntimeGuard::new(self.runtime.clone()); - // Then, poll any tasks that might be pending in the scope - for task in work.tasks { - // During suspense, we only want to run tasks that are suspended - if self.runtime.suspended_tasks.borrow().contains(&task) { - let _ = self.runtime.handle_task_wakeup(task); - } - } - // If the scope is dirty, run the scope and get the mutations - if work.rerun_scope { - let new_nodes = self.run_scope(work.scope.id); - - self.diff_scope(&mut NoOpMutations, work.scope.id, new_nodes); - } + self.diff_scope(&mut NoOpMutations, work.scope.id, new_nodes); } } } diff --git a/packages/fullstack/examples/static-hydrated/src/main.rs b/packages/fullstack/examples/static-hydrated/src/main.rs index e9d2c77c7..3fb981cee 100644 --- a/packages/fullstack/examples/static-hydrated/src/main.rs +++ b/packages/fullstack/examples/static-hydrated/src/main.rs @@ -36,7 +36,7 @@ async fn main() { } // Hydrate the page -#[cfg(not(feature = "server"))] +#[cfg(all(feature = "web", not(feature = "server")))] fn main() { dioxus_web::launch_with_props( dioxus_fullstack::router::RouteWithCfg::,