From cb4c46154d6b145c9fdb98e34d1e43bd2c211437 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 4 Aug 2023 14:12:59 -0700 Subject: [PATCH] fix scope context --- examples/clock.rs | 4 +- packages/core/src/arena.rs | 10 +++-- packages/core/src/diff.rs | 2 +- packages/core/src/runtime.rs | 64 ++++++++++++++++++------------ packages/core/src/scope_arena.rs | 11 ++++- packages/core/src/scope_context.rs | 23 ++++++++--- packages/core/src/scopes.rs | 14 +++++-- 7 files changed, 83 insertions(+), 45 deletions(-) diff --git a/examples/clock.rs b/examples/clock.rs index 505a5e87c..e59538edc 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -3,15 +3,13 @@ //! The example from the README.md. use dioxus::prelude::*; -use dioxus_signals::{use_init_signal_rt, use_signal}; +use dioxus_signals::use_signal; fn main() { dioxus_desktop::launch(app); } fn app(cx: Scope) -> Element { - use_init_signal_rt(cx); - let mut count = use_signal(cx, || 0); use_future!(cx, || async move { diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 25b040643..30a9cba8f 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -110,11 +110,13 @@ impl VirtualDom { // Drop all the hooks once the children are dropped // this means we'll drop hooks bottom-up scope.hooks.get_mut().clear(); - let context = scope.context(); + { + let context = scope.context(); - // Drop all the futures once the hooks are dropped - for task_id in context.spawned_tasks.borrow_mut().drain() { - context.tasks.remove(task_id); + // Drop all the futures once the hooks are dropped + for task_id in context.spawned_tasks.borrow_mut().drain() { + context.tasks.remove(task_id); + } } self.scopes.remove(id.0); diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index da1716e70..e3ca19c3a 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -209,7 +209,7 @@ impl<'b> VirtualDom { self.diff_scope(scope_id); self.dirty_scopes.remove(&DirtyScope { - height: self.runtime.scope_contexts[scope_id.0].height, + height: self.runtime.get_context(scope_id).unwrap().height, id: scope_id, }); } diff --git a/packages/core/src/runtime.rs b/packages/core/src/runtime.rs index 7a9921e99..7b9c7c05c 100644 --- a/packages/core/src/runtime.rs +++ b/packages/core/src/runtime.rs @@ -1,20 +1,14 @@ -use std::cell::RefCell; +use std::cell::{Ref, RefCell}; -use crate::{ - innerlude::{DirtyScope, Scheduler, SchedulerMsg}, - scope_context::ScopeContext, - scopes::{ScopeId, ScopeState}, -}; -use rustc_hash::FxHashSet; -use slab::Slab; -use std::{collections::BTreeSet, rc::Rc}; +use crate::{innerlude::Scheduler, scope_context::ScopeContext, scopes::ScopeId}; +use std::rc::Rc; thread_local! { - static RUNTIMES: RefCell> = RefCell::new(vec![]); + static RUNTIMES: RefCell>> = RefCell::new(vec![]); } /// Pushes a new scope onto the stack -pub(crate) fn push_runtime(runtime: Runtime) { +pub(crate) fn push_runtime(runtime: Rc) { RUNTIMES.with(|stack| stack.borrow_mut().push(runtime)); } @@ -30,7 +24,7 @@ where { RUNTIMES.with(|stack| { let stack = stack.borrow(); - stack.last().map(f) + stack.last().map(|r| f(&**r)) }) } @@ -42,28 +36,50 @@ where with_runtime(|runtime| { runtime .current_scope_id() - .and_then(|scope| runtime.get_context(scope).map(f)) + .and_then(|scope| runtime.get_context(scope).map(|sc| f(&*sc))) }) .flatten() } pub struct Runtime { - pub(crate) scope_contexts: Slab, + pub(crate) scope_contexts: RefCell>>, pub(crate) scheduler: Rc, // While diffing we need some sort of way of breaking off a stream of suspended mutations. pub(crate) scope_stack: RefCell>, } +impl Drop for Runtime { + fn drop(&mut self) { + // todo: do this better + pop_runtime(); + } +} + impl Runtime { pub(crate) fn new(scheduler: Rc) -> Rc { - Rc::new(Self { + let runtime = Rc::new(Self { scheduler, scope_contexts: Default::default(), scope_stack: Default::default(), - }) + }); + push_runtime(runtime.clone()); + runtime + } + + /// Create a scope context. This slab is synchronized with the scope slab. + pub(crate) fn create_context_at(&self, id: ScopeId, context: ScopeContext) { + let mut contexts = self.scope_contexts.borrow_mut(); + if contexts.len() <= id.0 { + contexts.resize_with(id.0 + 1, Default::default); + } + contexts[id.0] = Some(context); + } + + pub(crate) fn remove_context(&self, id: ScopeId) { + self.scope_contexts.borrow_mut()[id.0] = None; } /// Get the current scope id @@ -71,17 +87,13 @@ impl Runtime { self.scope_stack.borrow().last().copied() } - /// Get the state for any scope given its ID + /// Get the context for any scope given its ID /// /// This is useful for inserting or removing contexts from a scope, or rendering out its root node - pub fn get_context(&self, id: ScopeId) -> Option<&ScopeContext> { - self.scope_contexts.get(id.0).map(|f| &*f) - } - - /// Get the single scope at the top of the Runtime tree that will always be around - /// - /// This scope has a ScopeId of 0 and is the root of the tree - pub fn base_context(&self) -> &ScopeContext { - self.get_context(ScopeId(0)).unwrap() + pub fn get_context(&self, id: ScopeId) -> Option> { + Ref::filter_map(self.scope_contexts.borrow(), |contexts| { + contexts.get(id.0).and_then(|f| f.as_ref()) + }) + .ok() } } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 1a64612bf..2d5e09b0b 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -3,6 +3,7 @@ use crate::{ bump_frame::BumpFrame, innerlude::DirtyScope, nodes::RenderReturn, + scope_context::ScopeContext, scopes::{ScopeId, ScopeState}, virtual_dom::VirtualDom, }; @@ -20,7 +21,7 @@ impl VirtualDom { let entry = self.scopes.vacant_entry(); let id = ScopeId(entry.key()); - entry.insert(ScopeState { + let scope = entry.insert(ScopeState { runtime: self.runtime.clone(), context_id: id, @@ -35,7 +36,13 @@ impl VirtualDom { borrowed_props: Default::default(), attributes_to_drop: Default::default(), - }) + }); + + let context = + ScopeContext::new(name, id, parent_id, height, self.runtime.scheduler.clone()); + self.runtime.create_context_at(id, context); + + scope } pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn { diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index e1a64bc2b..340845afc 100644 --- a/packages/core/src/scope_context.rs +++ b/packages/core/src/scope_context.rs @@ -32,12 +32,23 @@ pub struct ScopeContext { } impl ScopeContext { - pub fn name(&self) -> &str { - self.name - } - - pub fn height(&self) -> u32 { - self.height + pub(crate) fn new( + name: &'static str, + id: ScopeId, + parent_id: Option, + height: u32, + tasks: Rc, + ) -> Self { + Self { + name, + id, + parent_id, + height, + suspended: Cell::new(false), + shared_contexts: RefCell::new(vec![]), + tasks, + spawned_tasks: RefCell::new(FxHashSet::default()), + } } pub fn parent_id(&self) -> Option { diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index d1897331a..e17e08aa9 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -13,7 +13,7 @@ use crate::{ use bumpalo::{boxed::Box as BumpBox, Bump}; use std::{ any::Any, - cell::{Cell, RefCell, UnsafeCell}, + cell::{Cell, Ref, RefCell, UnsafeCell}, fmt::{Arguments, Debug}, future::Future, rc::Rc, @@ -84,8 +84,14 @@ pub struct ScopeState { pub(crate) props: Option>>, } +impl Drop for ScopeState { + fn drop(&mut self) { + self.runtime.remove_context(self.context_id); + } +} + impl<'src> ScopeState { - pub(crate) fn context(&self) -> &ScopeContext { + pub(crate) fn context(&self) -> Ref<'_, ScopeContext> { self.runtime.get_context(self.context_id).unwrap() } @@ -494,7 +500,9 @@ impl<'src> ScopeState { /// Mark this component as suspended and then return None pub fn suspend(&self) -> Option { - self.context().suspend() + let cx = self.context(); + cx.suspend(); + None } /// Store a value between renders. The foundational hook for all other hooks.