improve signal copy runtime

This commit is contained in:
Evan Almloff committed 2023-08-04 13:53:05 -07:00
1 parent 3d4c5d7c98
commit 04cdb14e5b
14 files changed
+878 -319

No files matched your search

+4 -3
View File
@@ -91,7 +91,7 @@ impl VirtualDom {
// Note: This will not remove any ids from the arena
pub(crate) fn drop_scope(&mut self, id: ScopeId, recursive: bool) {
self.dirty_scopes.remove(&DirtyScope {
height: self.scopes[id.0].height,
height: self.scopes[id.0].height(),
id,
});
@@ -110,10 +110,11 @@ 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();
// Drop all the futures once the hooks are dropped
for task_id in scope.spawned_tasks.borrow_mut().drain() {
scope.tasks.remove(task_id);
for task_id in context.spawned_tasks.borrow_mut().drain() {
context.tasks.remove(task_id);
}
self.scopes.remove(id.0);
+3 -3
View File
@@ -66,9 +66,9 @@ impl<'b> VirtualDom {
///
/// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created.
pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &'b VNode<'b>) -> usize {
self.scope_stack.push(scope);
self.runtime.scope_stack.borrow_mut().push(scope);
let out = self.create(template);
self.scope_stack.pop();
self.runtime.scope_stack.borrow_mut().pop();
out
}
@@ -522,7 +522,7 @@ impl<'b> VirtualDom {
.take()
.map(|props| {
let unbounded_props: Box<dyn AnyProps> = unsafe { std::mem::transmute(props) };
self.new_scope(unbounded_props, component.name).id
self.new_scope(unbounded_props, component.name).context().id
})
.unwrap_or_else(|| component.scope.get().unwrap())
}
+28 -9
View File
@@ -15,9 +15,8 @@ use DynamicNode::*;
impl<'b> VirtualDom {
pub(super) fn diff_scope(&mut self, scope: ScopeId) {
let scope_state = &mut self.scopes[scope.0];
self.scope_stack.push(scope);
self.runtime.scope_stack.borrow_mut().push(scope);
let scope_state = &mut self.get_scope(scope).unwrap();
unsafe {
// Load the old and new bump arenas
let old = scope_state
@@ -47,7 +46,7 @@ impl<'b> VirtualDom {
(Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]),
};
}
self.scope_stack.pop();
self.runtime.scope_stack.borrow_mut().pop();
}
fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) {
@@ -210,7 +209,7 @@ impl<'b> VirtualDom {
self.diff_scope(scope_id);
self.dirty_scopes.remove(&DirtyScope {
height: self.scopes[scope_id.0].height,
height: self.runtime.scope_contexts[scope_id.0].height,
id: scope_id,
});
}
@@ -714,7 +713,12 @@ impl<'b> VirtualDom {
Component(comp) => {
let scope = comp.scope.get().unwrap();
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
match unsafe {
self.get_scope(scope)
.unwrap()
.root_node()
.extend_lifetime_ref()
} {
RenderReturn::Ready(node) => self.push_all_real_nodes(node),
RenderReturn::Aborted(_node) => todo!(),
}
@@ -915,7 +919,12 @@ impl<'b> VirtualDom {
.expect("VComponents to always have a scope");
// Remove the component from the dom
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
match unsafe {
self.get_scope(scope)
.unwrap()
.root_node()
.extend_lifetime_ref()
} {
RenderReturn::Ready(t) => self.remove_node(t, gen_muts),
RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts),
};
@@ -936,7 +945,12 @@ impl<'b> VirtualDom {
Some(Placeholder(t)) => t.id.get().unwrap(),
Some(Component(comp)) => {
let scope = comp.scope.get().unwrap();
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
match unsafe {
self.get_scope(scope)
.unwrap()
.root_node()
.extend_lifetime_ref()
} {
RenderReturn::Ready(t) => self.find_first_element(t),
_ => todo!("cannot handle nonstandard nodes"),
}
@@ -952,7 +966,12 @@ impl<'b> VirtualDom {
Some(Placeholder(t)) => t.id.get().unwrap(),
Some(Component(comp)) => {
let scope = comp.scope.get().unwrap();
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
match unsafe {
self.get_scope(scope)
.unwrap()
.root_node()
.extend_lifetime_ref()
} {
RenderReturn::Ready(t) => self.find_last_element(t),
_ => todo!("cannot handle nonstandard nodes"),
}
+8 -3
View File
@@ -14,8 +14,10 @@ mod lazynodes;
mod mutations;
mod nodes;
mod properties;
mod runtime;
mod scheduler;
mod scope_arena;
mod scope_context;
mod scopes;
mod virtual_dom;
@@ -31,6 +33,7 @@ pub(crate) mod innerlude {
pub use crate::nodes::*;
pub use crate::properties::*;
pub use crate::scheduler::*;
pub use crate::scope_context::*;
pub use crate::scopes::*;
pub use crate::virtual_dom::*;
@@ -81,9 +84,11 @@ pub use crate::innerlude::{
/// This includes types like [`Scope`], [`Element`], and [`Component`].
pub mod prelude {
pub use crate::innerlude::{
fc_to_builder, AnyValue, Component, Element, Event, EventHandler, Fragment,
IntoAttributeValue, LazyNodes, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId,
Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
consume_context, current_scope_id, fc_to_builder, has_context, provide_context,
provide_root_context, schedule_update_any, suspend, throw, AnyValue, Component, Element,
Event, EventHandler, Fragment, IntoAttributeValue, LazyNodes, Properties, Scope, ScopeId,
ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw, VNode,
VirtualDom,
};
}
+87
View File
@@ -0,0 +1,87 @@
use std::cell::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};
thread_local! {
static RUNTIMES: RefCell<Vec<Runtime>> = RefCell::new(vec![]);
}
/// Pushes a new scope onto the stack
pub(crate) fn push_runtime(runtime: Runtime) {
RUNTIMES.with(|stack| stack.borrow_mut().push(runtime));
}
/// Pops a scope off the stack
pub(crate) fn pop_runtime() {
RUNTIMES.with(|stack| stack.borrow_mut().pop());
}
/// Runs a function with the current runtime
pub(crate) fn with_runtime<F, R>(f: F) -> Option<R>
where
F: FnOnce(&Runtime) -> R,
{
RUNTIMES.with(|stack| {
let stack = stack.borrow();
stack.last().map(f)
})
}
/// Runs a function with the current scope
pub(crate) fn with_current_scope<F, R>(f: F) -> Option<R>
where
F: FnOnce(&ScopeContext) -> R,
{
with_runtime(|runtime| {
runtime
.current_scope_id()
.and_then(|scope| runtime.get_context(scope).map(f))
})
.flatten()
}
pub struct Runtime {
pub(crate) scope_contexts: Slab<ScopeContext>,
pub(crate) scheduler: Rc<Scheduler>,
// While diffing we need some sort of way of breaking off a stream of suspended mutations.
pub(crate) scope_stack: RefCell<Vec<ScopeId>>,
}
impl Runtime {
pub(crate) fn new(scheduler: Rc<Scheduler>) -> Rc<Self> {
Rc::new(Self {
scheduler,
scope_contexts: Default::default(),
scope_stack: Default::default(),
})
}
/// Get the current scope id
pub fn current_scope_id(&self) -> Option<ScopeId> {
self.scope_stack.borrow().last().copied()
}
/// Get the state 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()
}
}
+3 -3
View File
@@ -7,7 +7,7 @@ impl VirtualDom {
/// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
/// queue
pub(crate) fn handle_task_wakeup(&mut self, id: TaskId) {
let mut tasks = self.scheduler.tasks.borrow_mut();
let mut tasks = self.runtime.scheduler.tasks.borrow_mut();
let task = match tasks.get(id.0) {
Some(task) => task,
@@ -20,8 +20,8 @@ impl VirtualDom {
// If the task completes...
if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
// Remove it from the scope so we dont try to double drop it when the scope dropes
let scope = &self.scopes[task.scope.0];
scope.spawned_tasks.borrow_mut().remove(&id);
let scope = &self.get_scope(task.scope).unwrap();
scope.context().spawned_tasks.borrow_mut().remove(&id);
// Remove it from the scheduler
tasks.try_remove(id.0);
+21 -26
View File
@@ -13,35 +13,29 @@ impl VirtualDom {
props: Box<dyn AnyProps<'static>>,
name: &'static str,
) -> &ScopeState {
let parent = self.acquire_current_scope_raw();
let parent_id = self.runtime.current_scope_id();
let height = parent_id
.and_then(|parent_id| self.get_scope(parent_id).map(|f| f.context().height + 1))
.unwrap_or(0);
let entry = self.scopes.vacant_entry();
let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) };
let id = ScopeId(entry.key());
entry.insert(Box::new(ScopeState {
parent,
id,
height,
name,
entry.insert(ScopeState {
runtime: self.runtime.clone(),
context_id: id,
props: Some(props),
tasks: self.scheduler.clone(),
node_arena_1: BumpFrame::new(0),
node_arena_2: BumpFrame::new(0),
spawned_tasks: Default::default(),
suspended: Default::default(),
render_cnt: Default::default(),
hooks: Default::default(),
hook_idx: Default::default(),
shared_contexts: Default::default(),
borrowed_props: Default::default(),
attributes_to_drop: Default::default(),
}))
}
fn acquire_current_scope_raw(&self) -> Option<*const ScopeState> {
let id = self.scope_stack.last().copied()?;
let scope = self.scopes.get(id.0)?;
Some(scope.as_ref())
})
}
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
@@ -51,10 +45,10 @@ impl VirtualDom {
self.ensure_drop_safety(scope_id);
let new_nodes = unsafe {
self.scopes[scope_id.0].previous_frame().bump_mut().reset();
let scope = &self.scopes[scope_id.0];
scope.suspended.set(false);
scope.previous_frame().bump_mut().reset();
scope.context().suspended.set(false);
scope.hook_idx.set(0);
@@ -77,18 +71,19 @@ impl VirtualDom {
// And move the render generation forward by one
scope.render_cnt.set(scope.render_cnt.get() + 1);
let context = scope.context();
// remove this scope from dirty scopes
self.dirty_scopes.remove(&DirtyScope {
height: scope.height,
id: scope.id,
height: context.height,
id: context.id,
});
if scope.suspended.get() {
if context.suspended.get() {
if matches!(allocated, RenderReturn::Aborted(_)) {
self.suspended_scopes.insert(scope.id);
self.suspended_scopes.insert(context.id);
}
} else if !self.suspended_scopes.is_empty() {
_ = self.suspended_scopes.remove(&scope.id);
_ = self.suspended_scopes.remove(&context.id);
}
// rebind the lifetime now that its stored internally
+288
View File
@@ -0,0 +1,288 @@
use crate::{
innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
runtime::{with_current_scope, with_runtime},
Element, ScopeId, TaskId,
};
use rustc_hash::FxHashSet;
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell},
fmt::Debug,
future::Future,
rc::Rc,
sync::Arc,
};
/// A component's state separate from its props.
///
/// This struct exists to provide a common interface for all scopes without relying on generics.
pub struct ScopeContext {
pub(crate) name: &'static str,
pub(crate) id: ScopeId,
pub(crate) parent_id: Option<ScopeId>,
pub(crate) height: u32,
pub(crate) suspended: Cell<bool>,
pub(crate) shared_contexts: RefCell<Vec<(TypeId, Box<dyn Any>)>>,
pub(crate) tasks: Rc<Scheduler>,
pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
}
impl ScopeContext {
pub fn name(&self) -> &str {
self.name
}
pub fn height(&self) -> u32 {
self.height
}
pub fn parent_id(&self) -> Option<ScopeId> {
self.parent_id
}
pub fn scope_id(&self) -> ScopeId {
self.id
}
/// Create a subscription that schedules a future render for the reference component
///
/// ## Notice: you should prefer using [`Self::schedule_update_any`] and [`Self::scope_id`]
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
}
/// Schedule an update for any component given its [`ScopeId`].
///
/// A component's [`ScopeId`] can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
///
/// This method should be used when you want to schedule an update for a component
pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
let chan = self.tasks.sender.clone();
Arc::new(move |id| {
chan.unbounded_send(SchedulerMsg::Immediate(id)).unwrap();
})
}
/// Mark this scope as dirty, and schedule a render for it.
pub fn needs_update(&self) {
self.needs_update_any(self.scope_id());
}
/// Get the [`ScopeId`] of a mounted component.
///
/// `ScopeId` is not unique for the lifetime of the [`crate::VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
pub fn needs_update_any(&self, id: ScopeId) {
self.tasks
.sender
.unbounded_send(SchedulerMsg::Immediate(id))
.expect("Scheduler to exist if scope exists");
}
/// Return any context of type T if it exists on this scope
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
self.shared_contexts
.borrow()
.iter()
.find(|(k, _)| *k == TypeId::of::<T>())
.map(|(_, v)| v)?
.downcast_ref::<T>()
.cloned()
}
/// Try to retrieve a shared state with type `T` from any parent scope.
///
/// Clones the state if it exists.
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
if let Some(this_ctx) = self.has_context() {
return Some(this_ctx);
}
let mut search_parent = self.parent_id;
with_runtime(|runtime| {
let mut result = None;
while let Some(parent_id) = search_parent {
let parent = runtime.get_context(parent_id).unwrap();
if let Some(shared) = parent
.shared_contexts
.borrow()
.iter()
.find(|(k, _)| *k == TypeId::of::<T>())
{
result = shared.1.downcast_ref::<T>().cloned();
break;
}
search_parent = parent.parent_id;
}
result
})
.flatten()
}
/// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree.
///
/// This is a "fundamental" operation and should only be called during initialization of a hook.
///
/// For a hook that provides the same functionality, use `use_provide_context` and `use_context` instead.
///
/// # Example
///
/// ```rust, ignore
/// struct SharedState(&'static str);
///
/// static App: Component = |cx| {
/// cx.use_hook(|| cx.provide_context(SharedState("world")));
/// render!(Child {})
/// }
///
/// static Child: Component = |cx| {
/// let state = cx.consume_state::<SharedState>();
/// render!(div { "hello {state.0}" })
/// }
/// ```
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
let mut contexts = self.shared_contexts.borrow_mut();
// If the context exists, swap it out for the new value
for ctx in contexts.iter_mut() {
// Swap the ptr directly
if let Some(ctx) = ctx.1.downcast_mut::<T>() {
std::mem::swap(ctx, &mut value.clone());
return value;
}
}
// Else, just push it
contexts.push((TypeId::of::<T>(), Box::new(value.clone())));
value
}
/// Provide a context to the root and then consume it
///
/// This is intended for "global" state management solutions that would rather be implicit for the entire app.
/// Things like signal runtimes and routers are examples of "singletons" that would benefit from lazy initialization.
///
/// Note that you should be checking if the context existed before trying to provide a new one. Providing a context
/// when a context already exists will swap the context out for the new one, which may not be what you want.
pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
with_runtime(|runtime| {
// Walk upwards until there is no more parent - and tada we have the root
let mut parent = runtime.get_context(self.scope_id()).unwrap();
while let Some(next_parent) = parent.parent_id {
parent = runtime.get_context(next_parent).unwrap();
}
debug_assert_eq!(parent.scope_id(), ScopeId(0));
parent.provide_context(context)
})
.expect("Runtime to exist")
}
/// Pushes the future onto the poll queue to be polled after the component renders.
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
let id = self.tasks.spawn(self.id, fut);
self.spawned_tasks.borrow_mut().insert(id);
id
}
/// Spawns the future but does not return the [`TaskId`]
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
self.push_future(fut);
}
/// Spawn a future that Dioxus won't clean up when this component is unmounted
///
/// This is good for tasks that need to be run after the component has been dropped.
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
// The root scope will never be unmounted so we can just add the task at the top of the app
let id = self.tasks.spawn(ScopeId(0), fut);
// wake up the scheduler if it is sleeping
self.tasks
.sender
.unbounded_send(SchedulerMsg::TaskNotified(id))
.expect("Scheduler should exist");
self.spawned_tasks.borrow_mut().insert(id);
id
}
/// Informs the scheduler that this task is no longer needed and should be removed.
///
/// This drops the task immediately.
pub fn remove_future(&self, id: TaskId) {
self.tasks.remove(id);
}
/// Inject an error into the nearest error boundary and quit rendering
///
/// The error doesn't need to implement Error or any specific traits since the boundary
/// itself will downcast the error into a trait object.
pub fn throw(&self, error: impl Debug + 'static) -> Option<()> {
if let Some(cx) = self.consume_context::<Rc<ErrorBoundary>>() {
cx.insert_error(self.scope_id(), Box::new(error));
}
// Always return none during a throw
None
}
/// Mark this component as suspended and then return None
pub fn suspend(&self) -> Option<Element> {
self.suspended.set(true);
None
}
}
/// Schedule an update for any component given its [`ScopeId`].
///
/// A component's [`ScopeId`] can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
///
/// This method should be used when you want to schedule an update for a component
pub fn schedule_update_any() -> Option<Arc<dyn Fn(ScopeId) + Send + Sync>> {
with_current_scope(|cx| cx.schedule_update_any())
}
/// Get the current scope id
pub fn current_scope_id() -> Option<ScopeId> {
with_runtime(|rt| rt.current_scope_id()).flatten()
}
/// Consume context from the current scope
pub fn consume_context<T: 'static + Clone>() -> Option<T> {
with_current_scope(|cx| cx.consume_context::<T>()).flatten()
}
/// Check if the current scope has a context
pub fn has_context<T: 'static + Clone>() -> Option<T> {
with_current_scope(|cx| cx.has_context::<T>()).flatten()
}
/// Provide context to the current scope
pub fn provide_context<T: 'static + Clone>(value: T) -> Option<T> {
with_current_scope(|cx| cx.provide_context(value))
}
/// Provide a context to the root scope
pub fn provide_root_context<T: 'static + Clone>(value: T) -> Option<T> {
with_current_scope(|cx| cx.provide_root_context(value))
}
/// Suspends the current component
pub fn suspend() -> Option<Element<'static>> {
with_current_scope(|cx| {
cx.suspend();
});
None
}
/// Throw an error into the nearest error boundary
pub fn throw(error: impl Debug + 'static) -> Option<()> {
with_current_scope(|cx| cx.throw(error)).flatten()
}
+30 -101
View File
@@ -2,16 +2,17 @@ use crate::{
any_props::AnyProps,
any_props::VProps,
bump_frame::BumpFrame,
innerlude::ErrorBoundary,
innerlude::{DynamicNode, EventHandler, VComponent, VText},
innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
lazynodes::LazyNodes,
nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
runtime::Runtime,
scope_context::ScopeContext,
AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId,
};
use bumpalo::{boxed::Box as BumpBox, Bump};
use rustc_hash::FxHashSet;
use std::{
any::{Any, TypeId},
any::Any,
cell::{Cell, RefCell, UnsafeCell},
fmt::{Arguments, Debug},
future::Future,
@@ -66,26 +67,17 @@ pub struct ScopeId(pub usize);
///
/// This struct exists to provide a common interface for all scopes without relying on generics.
pub struct ScopeState {
pub(crate) runtime: Rc<Runtime>,
pub(crate) context_id: ScopeId,
pub(crate) render_cnt: Cell<usize>,
pub(crate) name: &'static str,
pub(crate) node_arena_1: BumpFrame,
pub(crate) node_arena_2: BumpFrame,
pub(crate) parent: Option<*const ScopeState>,
pub(crate) id: ScopeId,
pub(crate) height: u32,
pub(crate) suspended: Cell<bool>,
pub(crate) hooks: RefCell<Vec<Box<UnsafeCell<dyn Any>>>>,
pub(crate) hook_idx: Cell<usize>,
pub(crate) shared_contexts: RefCell<Vec<(TypeId, Box<dyn Any>)>>,
pub(crate) tasks: Rc<Scheduler>,
pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
@@ -93,6 +85,10 @@ pub struct ScopeState {
}
impl<'src> ScopeState {
pub(crate) fn context(&self) -> &ScopeContext {
self.runtime.get_context(self.context_id).unwrap()
}
pub(crate) fn current_frame(&self) -> &BumpFrame {
match self.render_cnt.get() % 2 {
0 => &self.node_arena_1,
@@ -111,7 +107,7 @@ impl<'src> ScopeState {
/// Get the name of this component
pub fn name(&self) -> &str {
self.name
self.context().name
}
/// Get the current render since the inception of this component
@@ -174,7 +170,7 @@ impl<'src> ScopeState {
/// assert_eq!(base.height(), 0);
/// ```
pub fn height(&self) -> u32 {
self.height
self.context().height
}
/// Get the Parent of this [`Scope`] within this Dioxus [`crate::VirtualDom`].
@@ -193,9 +189,9 @@ impl<'src> ScopeState {
///
/// assert_eq!(base.parent(), None);
/// ```
pub fn parent(&self) -> Option<ScopeId> {
pub fn parent_id(&self) -> Option<ScopeId> {
// safety: the pointer to our parent is *always* valid thanks to the bump arena
self.parent.map(|p| unsafe { &*p }.id)
self.context().parent_id()
}
/// Get the ID of this Scope within this Dioxus [`crate::VirtualDom`].
@@ -212,15 +208,14 @@ impl<'src> ScopeState {
/// assert_eq!(base.scope_id(), 0);
/// ```
pub fn scope_id(&self) -> ScopeId {
self.id
self.context().scope_id()
}
/// Create a subscription that schedules a future render for the reference component
///
/// ## Notice: you should prefer using [`Self::schedule_update_any`] and [`Self::scope_id`]
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
self.context().schedule_update()
}
/// Schedule an update for any component given its [`ScopeId`].
@@ -229,61 +224,31 @@ impl<'src> ScopeState {
///
/// This method should be used when you want to schedule an update for a component
pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
let chan = self.tasks.sender.clone();
Arc::new(move |id| {
chan.unbounded_send(SchedulerMsg::Immediate(id)).unwrap();
})
self.context().schedule_update_any()
}
/// Mark this scope as dirty, and schedule a render for it.
pub fn needs_update(&self) {
self.needs_update_any(self.scope_id());
self.context().needs_update()
}
/// Get the [`ScopeId`] of a mounted component.
///
/// `ScopeId` is not unique for the lifetime of the [`crate::VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
pub fn needs_update_any(&self, id: ScopeId) {
self.tasks
.sender
.unbounded_send(SchedulerMsg::Immediate(id))
.expect("Scheduler to exist if scope exists");
self.context().needs_update_any(id)
}
/// Return any context of type T if it exists on this scope
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
self.shared_contexts
.borrow()
.iter()
.find(|(k, _)| *k == TypeId::of::<T>())
.map(|(_, v)| v)?
.downcast_ref::<T>()
.cloned()
self.context().has_context()
}
/// Try to retrieve a shared state with type `T` from any parent scope.
///
/// Clones the state if it exists.
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
if let Some(this_ctx) = self.has_context() {
return Some(this_ctx);
}
let mut search_parent = self.parent;
while let Some(parent_ptr) = search_parent {
// safety: all parent pointers are valid thanks to the bump arena
let parent = unsafe { &*parent_ptr };
if let Some(shared) = parent
.shared_contexts
.borrow()
.iter()
.find(|(k, _)| *k == TypeId::of::<T>())
{
return shared.1.downcast_ref::<T>().cloned();
}
search_parent = parent.parent;
}
None
self.context().consume_context()
}
/// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree.
@@ -308,21 +273,7 @@ impl<'src> ScopeState {
/// }
/// ```
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
let mut contexts = self.shared_contexts.borrow_mut();
// If the context exists, swap it out for the new value
for ctx in contexts.iter_mut() {
// Swap the ptr directly
if let Some(ctx) = ctx.1.downcast_mut::<T>() {
std::mem::swap(ctx, &mut value.clone());
return value;
}
}
// Else, just push it
contexts.push((TypeId::of::<T>(), Box::new(value.clone())));
value
self.context().provide_context(value)
}
/// Provide a context to the root and then consume it
@@ -333,52 +284,31 @@ impl<'src> ScopeState {
/// Note that you should be checking if the context existed before trying to provide a new one. Providing a context
/// when a context already exists will swap the context out for the new one, which may not be what you want.
pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
let mut parent = self;
// Walk upwards until there is no more parent - and tada we have the root
while let Some(next_parent) = parent.parent {
parent = unsafe { &*next_parent };
debug_assert_eq!(parent.scope_id(), ScopeId(0));
}
parent.provide_context(context)
self.context().provide_root_context(context)
}
/// Pushes the future onto the poll queue to be polled after the component renders.
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
let id = self.tasks.spawn(self.id, fut);
self.spawned_tasks.borrow_mut().insert(id);
id
self.context().push_future(fut)
}
/// Spawns the future but does not return the [`TaskId`]
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
self.push_future(fut);
self.context().spawn(fut);
}
/// Spawn a future that Dioxus won't clean up when this component is unmounted
///
/// This is good for tasks that need to be run after the component has been dropped.
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
// The root scope will never be unmounted so we can just add the task at the top of the app
let id = self.tasks.spawn(ScopeId(0), fut);
// wake up the scheduler if it is sleeping
self.tasks
.sender
.unbounded_send(SchedulerMsg::TaskNotified(id))
.expect("Scheduler should exist");
self.spawned_tasks.borrow_mut().insert(id);
id
self.context().spawn_forever(fut)
}
/// Informs the scheduler that this task is no longer needed and should be removed.
///
/// This drops the task immediately.
pub fn remove_future(&self, id: TaskId) {
self.tasks.remove(id);
self.context().remove_future(id);
}
/// Take a lazy [`crate::VNode`] structure and actually build it with the context of the efficient [`bumpalo::Bump`] allocator.
@@ -481,7 +411,7 @@ impl<'src> ScopeState {
/// fn(Scope<Props>) -> Element;
/// async fn(Scope<Props<'_>>) -> Element;
/// ```
pub fn component<P>(
pub fn component<P: 'static>(
&'src self,
component: fn(Scope<'src, P>) -> Element<'src>,
props: P,
@@ -564,8 +494,7 @@ impl<'src> ScopeState {
/// Mark this component as suspended and then return None
pub fn suspend(&self) -> Option<Element> {
self.suspended.set(true);
None
self.context().suspend()
}
/// Store a value between renders. The foundational hook for all other hooks.
+19 -17
View File
@@ -9,6 +9,7 @@ use crate::{
mutations::Mutation,
nodes::RenderReturn,
nodes::{Template, TemplateId},
runtime::Runtime,
scopes::{ScopeId, ScopeState},
AttributeValue, Element, Event, Scope,
};
@@ -174,24 +175,24 @@ use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
/// }
/// ```
pub struct VirtualDom {
pub(crate) scopes: Slab<ScopeState>,
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
// Maps a template path to a map of byteindexes to templates
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
pub(crate) scopes: Slab<Box<ScopeState>>,
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
pub(crate) scheduler: Rc<Scheduler>,
// Every element is actually a dual reference - one to the template and the other to the dynamic node in that template
pub(crate) elements: Slab<ElementRef>,
// While diffing we need some sort of way of breaking off a stream of suspended mutations.
pub(crate) scope_stack: Vec<ScopeId>,
pub(crate) mutations: Mutations<'static>,
pub(crate) runtime: Rc<Runtime>,
// Currently suspended scopes
pub(crate) suspended_scopes: FxHashSet<ScopeId>,
pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
pub(crate) mutations: Mutations<'static>,
}
impl VirtualDom {
@@ -251,16 +252,16 @@ impl VirtualDom {
/// ```
pub fn new_with_props<P: 'static>(root: fn(Scope<P>) -> Element, root_props: P) -> Self {
let (tx, rx) = futures_channel::mpsc::unbounded();
let scheduler = Scheduler::new(tx);
let mut dom = Self {
rx,
scheduler: Scheduler::new(tx),
templates: Default::default(),
runtime: Runtime::new(scheduler),
scopes: Default::default(),
dirty_scopes: Default::default(),
templates: Default::default(),
elements: Default::default(),
scope_stack: Vec::new(),
dirty_scopes: BTreeSet::new(),
suspended_scopes: FxHashSet::default(),
mutations: Mutations::default(),
suspended_scopes: Default::default(),
};
let root = dom.new_scope(
@@ -281,7 +282,7 @@ impl VirtualDom {
///
/// This is useful for inserting or removing contexts from a scope, or rendering out its root node
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
self.scopes.get(id.0).map(|f| f.as_ref())
self.scopes.get(id.0)
}
/// Get the single scope at the top of the VirtualDom tree that will always be around
@@ -301,10 +302,10 @@ impl VirtualDom {
/// Manually mark a scope as requiring a re-render
///
/// Whenever the VirtualDom "works", it will re-render this scope
/// Whenever the Runtime "works", it will re-render this scope
pub fn mark_dirty(&mut self, id: ScopeId) {
if let Some(scope) = self.get_scope(id) {
let height = scope.height;
let height = scope.height();
self.dirty_scopes.insert(DirtyScope { height, id });
}
}
@@ -501,10 +502,11 @@ impl VirtualDom {
if sync.template.get().name.rsplit_once(':').unwrap().0
== template.name.rsplit_once(':').unwrap().0
{
let height = scope.height;
let context = scope.context();
let height = context.height;
self.dirty_scopes.insert(DirtyScope {
height,
id: scope.id,
id: context.id,
});
}
}