improve signal copy runtime
This commit is contained in:
1 parent
3d4c5d7c98
commit
04cdb14e5b
14 files changed
+878
-319
No files matched your search
@@ -91,7 +91,7 @@ impl VirtualDom {
|
|||||||
// Note: This will not remove any ids from the arena
|
// Note: This will not remove any ids from the arena
|
||||||
pub(crate) fn drop_scope(&mut self, id: ScopeId, recursive: bool) {
|
pub(crate) fn drop_scope(&mut self, id: ScopeId, recursive: bool) {
|
||||||
self.dirty_scopes.remove(&DirtyScope {
|
self.dirty_scopes.remove(&DirtyScope {
|
||||||
height: self.scopes[id.0].height,
|
height: self.scopes[id.0].height(),
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -110,10 +110,11 @@ impl VirtualDom {
|
|||||||
// Drop all the hooks once the children are dropped
|
// Drop all the hooks once the children are dropped
|
||||||
// this means we'll drop hooks bottom-up
|
// this means we'll drop hooks bottom-up
|
||||||
scope.hooks.get_mut().clear();
|
scope.hooks.get_mut().clear();
|
||||||
|
let context = scope.context();
|
||||||
|
|
||||||
// Drop all the futures once the hooks are dropped
|
// Drop all the futures once the hooks are dropped
|
||||||
for task_id in scope.spawned_tasks.borrow_mut().drain() {
|
for task_id in context.spawned_tasks.borrow_mut().drain() {
|
||||||
scope.tasks.remove(task_id);
|
context.tasks.remove(task_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.scopes.remove(id.0);
|
self.scopes.remove(id.0);
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ impl<'b> VirtualDom {
|
|||||||
///
|
///
|
||||||
/// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created.
|
/// 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 {
|
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);
|
let out = self.create(template);
|
||||||
self.scope_stack.pop();
|
self.runtime.scope_stack.borrow_mut().pop();
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,7 +522,7 @@ impl<'b> VirtualDom {
|
|||||||
.take()
|
.take()
|
||||||
.map(|props| {
|
.map(|props| {
|
||||||
let unbounded_props: Box<dyn AnyProps> = unsafe { std::mem::transmute(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())
|
.unwrap_or_else(|| component.scope.get().unwrap())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ use DynamicNode::*;
|
|||||||
|
|
||||||
impl<'b> VirtualDom {
|
impl<'b> VirtualDom {
|
||||||
pub(super) fn diff_scope(&mut self, scope: ScopeId) {
|
pub(super) fn diff_scope(&mut self, scope: ScopeId) {
|
||||||
let scope_state = &mut self.scopes[scope.0];
|
self.runtime.scope_stack.borrow_mut().push(scope);
|
||||||
|
let scope_state = &mut self.get_scope(scope).unwrap();
|
||||||
self.scope_stack.push(scope);
|
|
||||||
unsafe {
|
unsafe {
|
||||||
// Load the old and new bump arenas
|
// Load the old and new bump arenas
|
||||||
let old = scope_state
|
let old = scope_state
|
||||||
@@ -47,7 +46,7 @@ impl<'b> VirtualDom {
|
|||||||
(Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]),
|
(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) {
|
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.diff_scope(scope_id);
|
||||||
|
|
||||||
self.dirty_scopes.remove(&DirtyScope {
|
self.dirty_scopes.remove(&DirtyScope {
|
||||||
height: self.scopes[scope_id.0].height,
|
height: self.runtime.scope_contexts[scope_id.0].height,
|
||||||
id: scope_id,
|
id: scope_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -714,7 +713,12 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
Component(comp) => {
|
Component(comp) => {
|
||||||
let scope = comp.scope.get().unwrap();
|
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::Ready(node) => self.push_all_real_nodes(node),
|
||||||
RenderReturn::Aborted(_node) => todo!(),
|
RenderReturn::Aborted(_node) => todo!(),
|
||||||
}
|
}
|
||||||
@@ -915,7 +919,12 @@ impl<'b> VirtualDom {
|
|||||||
.expect("VComponents to always have a scope");
|
.expect("VComponents to always have a scope");
|
||||||
|
|
||||||
// Remove the component from the dom
|
// 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::Ready(t) => self.remove_node(t, gen_muts),
|
||||||
RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, 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(Placeholder(t)) => t.id.get().unwrap(),
|
||||||
Some(Component(comp)) => {
|
Some(Component(comp)) => {
|
||||||
let scope = comp.scope.get().unwrap();
|
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),
|
RenderReturn::Ready(t) => self.find_first_element(t),
|
||||||
_ => todo!("cannot handle nonstandard nodes"),
|
_ => todo!("cannot handle nonstandard nodes"),
|
||||||
}
|
}
|
||||||
@@ -952,7 +966,12 @@ impl<'b> VirtualDom {
|
|||||||
Some(Placeholder(t)) => t.id.get().unwrap(),
|
Some(Placeholder(t)) => t.id.get().unwrap(),
|
||||||
Some(Component(comp)) => {
|
Some(Component(comp)) => {
|
||||||
let scope = comp.scope.get().unwrap();
|
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),
|
RenderReturn::Ready(t) => self.find_last_element(t),
|
||||||
_ => todo!("cannot handle nonstandard nodes"),
|
_ => todo!("cannot handle nonstandard nodes"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ mod lazynodes;
|
|||||||
mod mutations;
|
mod mutations;
|
||||||
mod nodes;
|
mod nodes;
|
||||||
mod properties;
|
mod properties;
|
||||||
|
mod runtime;
|
||||||
mod scheduler;
|
mod scheduler;
|
||||||
mod scope_arena;
|
mod scope_arena;
|
||||||
|
mod scope_context;
|
||||||
mod scopes;
|
mod scopes;
|
||||||
mod virtual_dom;
|
mod virtual_dom;
|
||||||
|
|
||||||
@@ -31,6 +33,7 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate::nodes::*;
|
pub use crate::nodes::*;
|
||||||
pub use crate::properties::*;
|
pub use crate::properties::*;
|
||||||
pub use crate::scheduler::*;
|
pub use crate::scheduler::*;
|
||||||
|
pub use crate::scope_context::*;
|
||||||
pub use crate::scopes::*;
|
pub use crate::scopes::*;
|
||||||
pub use crate::virtual_dom::*;
|
pub use crate::virtual_dom::*;
|
||||||
|
|
||||||
@@ -81,9 +84,11 @@ pub use crate::innerlude::{
|
|||||||
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
fc_to_builder, AnyValue, Component, Element, Event, EventHandler, Fragment,
|
consume_context, current_scope_id, fc_to_builder, has_context, provide_context,
|
||||||
IntoAttributeValue, LazyNodes, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId,
|
provide_root_context, schedule_update_any, suspend, throw, AnyValue, Component, Element,
|
||||||
Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
|
Event, EventHandler, Fragment, IntoAttributeValue, LazyNodes, Properties, Scope, ScopeId,
|
||||||
|
ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw, VNode,
|
||||||
|
VirtualDom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
/// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
|
||||||
/// queue
|
/// queue
|
||||||
pub(crate) fn handle_task_wakeup(&mut self, id: TaskId) {
|
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) {
|
let task = match tasks.get(id.0) {
|
||||||
Some(task) => task,
|
Some(task) => task,
|
||||||
@@ -20,8 +20,8 @@ impl VirtualDom {
|
|||||||
// If the task completes...
|
// If the task completes...
|
||||||
if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
|
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
|
// Remove it from the scope so we dont try to double drop it when the scope dropes
|
||||||
let scope = &self.scopes[task.scope.0];
|
let scope = &self.get_scope(task.scope).unwrap();
|
||||||
scope.spawned_tasks.borrow_mut().remove(&id);
|
scope.context().spawned_tasks.borrow_mut().remove(&id);
|
||||||
|
|
||||||
// Remove it from the scheduler
|
// Remove it from the scheduler
|
||||||
tasks.try_remove(id.0);
|
tasks.try_remove(id.0);
|
||||||
|
|||||||
@@ -13,35 +13,29 @@ impl VirtualDom {
|
|||||||
props: Box<dyn AnyProps<'static>>,
|
props: Box<dyn AnyProps<'static>>,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
) -> &ScopeState {
|
) -> &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 entry = self.scopes.vacant_entry();
|
||||||
let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) };
|
|
||||||
let id = ScopeId(entry.key());
|
let id = ScopeId(entry.key());
|
||||||
|
|
||||||
entry.insert(Box::new(ScopeState {
|
entry.insert(ScopeState {
|
||||||
parent,
|
runtime: self.runtime.clone(),
|
||||||
id,
|
context_id: id,
|
||||||
height,
|
|
||||||
name,
|
|
||||||
props: Some(props),
|
props: Some(props),
|
||||||
tasks: self.scheduler.clone(),
|
|
||||||
node_arena_1: BumpFrame::new(0),
|
node_arena_1: BumpFrame::new(0),
|
||||||
node_arena_2: BumpFrame::new(0),
|
node_arena_2: BumpFrame::new(0),
|
||||||
spawned_tasks: Default::default(),
|
|
||||||
suspended: Default::default(),
|
|
||||||
render_cnt: Default::default(),
|
render_cnt: Default::default(),
|
||||||
hooks: Default::default(),
|
hooks: Default::default(),
|
||||||
hook_idx: Default::default(),
|
hook_idx: Default::default(),
|
||||||
shared_contexts: Default::default(),
|
|
||||||
borrowed_props: Default::default(),
|
borrowed_props: Default::default(),
|
||||||
attributes_to_drop: 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 {
|
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
|
||||||
@@ -51,10 +45,10 @@ impl VirtualDom {
|
|||||||
self.ensure_drop_safety(scope_id);
|
self.ensure_drop_safety(scope_id);
|
||||||
|
|
||||||
let new_nodes = unsafe {
|
let new_nodes = unsafe {
|
||||||
self.scopes[scope_id.0].previous_frame().bump_mut().reset();
|
|
||||||
|
|
||||||
let scope = &self.scopes[scope_id.0];
|
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);
|
scope.hook_idx.set(0);
|
||||||
|
|
||||||
@@ -77,18 +71,19 @@ impl VirtualDom {
|
|||||||
// And move the render generation forward by one
|
// And move the render generation forward by one
|
||||||
scope.render_cnt.set(scope.render_cnt.get() + 1);
|
scope.render_cnt.set(scope.render_cnt.get() + 1);
|
||||||
|
|
||||||
|
let context = scope.context();
|
||||||
// remove this scope from dirty scopes
|
// remove this scope from dirty scopes
|
||||||
self.dirty_scopes.remove(&DirtyScope {
|
self.dirty_scopes.remove(&DirtyScope {
|
||||||
height: scope.height,
|
height: context.height,
|
||||||
id: scope.id,
|
id: context.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if scope.suspended.get() {
|
if context.suspended.get() {
|
||||||
if matches!(allocated, RenderReturn::Aborted(_)) {
|
if matches!(allocated, RenderReturn::Aborted(_)) {
|
||||||
self.suspended_scopes.insert(scope.id);
|
self.suspended_scopes.insert(context.id);
|
||||||
}
|
}
|
||||||
} else if !self.suspended_scopes.is_empty() {
|
} 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
|
// rebind the lifetime now that its stored internally
|
||||||
|
|||||||
@@ -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
@@ -2,16 +2,17 @@ use crate::{
|
|||||||
any_props::AnyProps,
|
any_props::AnyProps,
|
||||||
any_props::VProps,
|
any_props::VProps,
|
||||||
bump_frame::BumpFrame,
|
bump_frame::BumpFrame,
|
||||||
|
innerlude::ErrorBoundary,
|
||||||
innerlude::{DynamicNode, EventHandler, VComponent, VText},
|
innerlude::{DynamicNode, EventHandler, VComponent, VText},
|
||||||
innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
|
|
||||||
lazynodes::LazyNodes,
|
lazynodes::LazyNodes,
|
||||||
nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
|
nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
|
||||||
|
runtime::Runtime,
|
||||||
|
scope_context::ScopeContext,
|
||||||
AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId,
|
AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId,
|
||||||
};
|
};
|
||||||
use bumpalo::{boxed::Box as BumpBox, Bump};
|
use bumpalo::{boxed::Box as BumpBox, Bump};
|
||||||
use rustc_hash::FxHashSet;
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::Any,
|
||||||
cell::{Cell, RefCell, UnsafeCell},
|
cell::{Cell, RefCell, UnsafeCell},
|
||||||
fmt::{Arguments, Debug},
|
fmt::{Arguments, Debug},
|
||||||
future::Future,
|
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.
|
/// This struct exists to provide a common interface for all scopes without relying on generics.
|
||||||
pub struct ScopeState {
|
pub struct ScopeState {
|
||||||
|
pub(crate) runtime: Rc<Runtime>,
|
||||||
|
pub(crate) context_id: ScopeId,
|
||||||
|
|
||||||
pub(crate) render_cnt: Cell<usize>,
|
pub(crate) render_cnt: Cell<usize>,
|
||||||
pub(crate) name: &'static str,
|
|
||||||
|
|
||||||
pub(crate) node_arena_1: BumpFrame,
|
pub(crate) node_arena_1: BumpFrame,
|
||||||
pub(crate) node_arena_2: 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) hooks: RefCell<Vec<Box<UnsafeCell<dyn Any>>>>,
|
||||||
pub(crate) hook_idx: Cell<usize>,
|
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) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
|
||||||
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
|
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
|
||||||
|
|
||||||
@@ -93,6 +85,10 @@ pub struct ScopeState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'src> 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 {
|
pub(crate) fn current_frame(&self) -> &BumpFrame {
|
||||||
match self.render_cnt.get() % 2 {
|
match self.render_cnt.get() % 2 {
|
||||||
0 => &self.node_arena_1,
|
0 => &self.node_arena_1,
|
||||||
@@ -111,7 +107,7 @@ impl<'src> ScopeState {
|
|||||||
|
|
||||||
/// Get the name of this component
|
/// Get the name of this component
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
self.name
|
self.context().name
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current render since the inception of this component
|
/// Get the current render since the inception of this component
|
||||||
@@ -174,7 +170,7 @@ impl<'src> ScopeState {
|
|||||||
/// assert_eq!(base.height(), 0);
|
/// assert_eq!(base.height(), 0);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn height(&self) -> u32 {
|
pub fn height(&self) -> u32 {
|
||||||
self.height
|
self.context().height
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the Parent of this [`Scope`] within this Dioxus [`crate::VirtualDom`].
|
/// Get the Parent of this [`Scope`] within this Dioxus [`crate::VirtualDom`].
|
||||||
@@ -193,9 +189,9 @@ impl<'src> ScopeState {
|
|||||||
///
|
///
|
||||||
/// assert_eq!(base.parent(), None);
|
/// 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
|
// 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`].
|
/// Get the ID of this Scope within this Dioxus [`crate::VirtualDom`].
|
||||||
@@ -212,15 +208,14 @@ impl<'src> ScopeState {
|
|||||||
/// assert_eq!(base.scope_id(), 0);
|
/// assert_eq!(base.scope_id(), 0);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn scope_id(&self) -> ScopeId {
|
pub fn scope_id(&self) -> ScopeId {
|
||||||
self.id
|
self.context().scope_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a subscription that schedules a future render for the reference component
|
/// 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`]
|
/// ## Notice: you should prefer using [`Self::schedule_update_any`] and [`Self::scope_id`]
|
||||||
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
|
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
|
||||||
let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
|
self.context().schedule_update()
|
||||||
Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Schedule an update for any component given its [`ScopeId`].
|
/// 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
|
/// 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> {
|
pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
|
||||||
let chan = self.tasks.sender.clone();
|
self.context().schedule_update_any()
|
||||||
Arc::new(move |id| {
|
|
||||||
chan.unbounded_send(SchedulerMsg::Immediate(id)).unwrap();
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark this scope as dirty, and schedule a render for it.
|
/// Mark this scope as dirty, and schedule a render for it.
|
||||||
pub fn needs_update(&self) {
|
pub fn needs_update(&self) {
|
||||||
self.needs_update_any(self.scope_id());
|
self.context().needs_update()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the [`ScopeId`] of a mounted component.
|
/// 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.
|
/// `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) {
|
pub fn needs_update_any(&self, id: ScopeId) {
|
||||||
self.tasks
|
self.context().needs_update_any(id)
|
||||||
.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
|
/// Return any context of type T if it exists on this scope
|
||||||
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
|
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||||
self.shared_contexts
|
self.context().has_context()
|
||||||
.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.
|
/// Try to retrieve a shared state with type `T` from any parent scope.
|
||||||
///
|
///
|
||||||
/// Clones the state if it exists.
|
/// Clones the state if it exists.
|
||||||
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||||
if let Some(this_ctx) = self.has_context() {
|
self.context().consume_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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree.
|
/// 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 {
|
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
|
||||||
let mut contexts = self.shared_contexts.borrow_mut();
|
self.context().provide_context(value)
|
||||||
|
|
||||||
// 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
|
/// 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
|
/// 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.
|
/// 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 {
|
pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
|
||||||
let mut parent = self;
|
self.context().provide_root_context(context)
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes the future onto the poll queue to be polled after the component renders.
|
/// 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 {
|
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
||||||
let id = self.tasks.spawn(self.id, fut);
|
self.context().push_future(fut)
|
||||||
self.spawned_tasks.borrow_mut().insert(id);
|
|
||||||
id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns the future but does not return the [`TaskId`]
|
/// Spawns the future but does not return the [`TaskId`]
|
||||||
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
|
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
|
/// 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.
|
/// 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 {
|
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
|
self.context().spawn_forever(fut)
|
||||||
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.
|
/// Informs the scheduler that this task is no longer needed and should be removed.
|
||||||
///
|
///
|
||||||
/// This drops the task immediately.
|
/// This drops the task immediately.
|
||||||
pub fn remove_future(&self, id: TaskId) {
|
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.
|
/// 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;
|
/// fn(Scope<Props>) -> Element;
|
||||||
/// async fn(Scope<Props<'_>>) -> Element;
|
/// async fn(Scope<Props<'_>>) -> Element;
|
||||||
/// ```
|
/// ```
|
||||||
pub fn component<P>(
|
pub fn component<P: 'static>(
|
||||||
&'src self,
|
&'src self,
|
||||||
component: fn(Scope<'src, P>) -> Element<'src>,
|
component: fn(Scope<'src, P>) -> Element<'src>,
|
||||||
props: P,
|
props: P,
|
||||||
@@ -564,8 +494,7 @@ impl<'src> ScopeState {
|
|||||||
|
|
||||||
/// Mark this component as suspended and then return None
|
/// Mark this component as suspended and then return None
|
||||||
pub fn suspend(&self) -> Option<Element> {
|
pub fn suspend(&self) -> Option<Element> {
|
||||||
self.suspended.set(true);
|
self.context().suspend()
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store a value between renders. The foundational hook for all other hooks.
|
/// Store a value between renders. The foundational hook for all other hooks.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::{
|
|||||||
mutations::Mutation,
|
mutations::Mutation,
|
||||||
nodes::RenderReturn,
|
nodes::RenderReturn,
|
||||||
nodes::{Template, TemplateId},
|
nodes::{Template, TemplateId},
|
||||||
|
runtime::Runtime,
|
||||||
scopes::{ScopeId, ScopeState},
|
scopes::{ScopeId, ScopeState},
|
||||||
AttributeValue, Element, Event, Scope,
|
AttributeValue, Element, Event, Scope,
|
||||||
};
|
};
|
||||||
@@ -174,24 +175,24 @@ use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct VirtualDom {
|
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
|
// Maps a template path to a map of byteindexes to templates
|
||||||
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
|
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
|
// 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>,
|
pub(crate) elements: Slab<ElementRef>,
|
||||||
|
|
||||||
// While diffing we need some sort of way of breaking off a stream of suspended mutations.
|
pub(crate) mutations: Mutations<'static>,
|
||||||
pub(crate) scope_stack: Vec<ScopeId>,
|
|
||||||
|
pub(crate) runtime: Rc<Runtime>,
|
||||||
|
|
||||||
// Currently suspended scopes
|
// Currently suspended scopes
|
||||||
pub(crate) suspended_scopes: FxHashSet<ScopeId>,
|
pub(crate) suspended_scopes: FxHashSet<ScopeId>,
|
||||||
|
|
||||||
pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
|
pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
|
||||||
|
|
||||||
pub(crate) mutations: Mutations<'static>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
@@ -251,16 +252,16 @@ impl VirtualDom {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn new_with_props<P: 'static>(root: fn(Scope<P>) -> Element, root_props: P) -> Self {
|
pub fn new_with_props<P: 'static>(root: fn(Scope<P>) -> Element, root_props: P) -> Self {
|
||||||
let (tx, rx) = futures_channel::mpsc::unbounded();
|
let (tx, rx) = futures_channel::mpsc::unbounded();
|
||||||
|
let scheduler = Scheduler::new(tx);
|
||||||
let mut dom = Self {
|
let mut dom = Self {
|
||||||
rx,
|
rx,
|
||||||
scheduler: Scheduler::new(tx),
|
runtime: Runtime::new(scheduler),
|
||||||
templates: Default::default(),
|
|
||||||
scopes: Default::default(),
|
scopes: Default::default(),
|
||||||
|
dirty_scopes: Default::default(),
|
||||||
|
templates: Default::default(),
|
||||||
elements: Default::default(),
|
elements: Default::default(),
|
||||||
scope_stack: Vec::new(),
|
|
||||||
dirty_scopes: BTreeSet::new(),
|
|
||||||
suspended_scopes: FxHashSet::default(),
|
|
||||||
mutations: Mutations::default(),
|
mutations: Mutations::default(),
|
||||||
|
suspended_scopes: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let root = dom.new_scope(
|
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
|
/// 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> {
|
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
|
/// 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
|
/// 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) {
|
pub fn mark_dirty(&mut self, id: ScopeId) {
|
||||||
if let Some(scope) = self.get_scope(id) {
|
if let Some(scope) = self.get_scope(id) {
|
||||||
let height = scope.height;
|
let height = scope.height();
|
||||||
self.dirty_scopes.insert(DirtyScope { height, id });
|
self.dirty_scopes.insert(DirtyScope { height, id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -501,10 +502,11 @@ impl VirtualDom {
|
|||||||
if sync.template.get().name.rsplit_once(':').unwrap().0
|
if sync.template.get().name.rsplit_once(':').unwrap().0
|
||||||
== template.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 {
|
self.dirty_scopes.insert(DirtyScope {
|
||||||
height,
|
height,
|
||||||
id: scope.id,
|
id: context.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-core = { workspace = true }
|
dioxus-core = { workspace = true }
|
||||||
slab = { workspace = true }
|
slab = { workspace = true }
|
||||||
|
bumpalo = { version = "3.6" }
|
||||||
@@ -0,0 +1,301 @@
|
|||||||
|
use std::{
|
||||||
|
cell::{Cell, Ref, RefCell, RefMut},
|
||||||
|
marker::PhantomData,
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bumpalo::Bump;
|
||||||
|
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```compile_fail
|
||||||
|
/// let data = String::from("hello world");
|
||||||
|
/// let store = Store::default();
|
||||||
|
/// let owner = store.owner();
|
||||||
|
/// let key = owner.insert(&data);
|
||||||
|
/// drop(data);
|
||||||
|
/// assert_eq!(*key.read(), "hello world");
|
||||||
|
/// ```
|
||||||
|
#[allow(unused)]
|
||||||
|
fn compile_fail() {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reused() {
|
||||||
|
let store = Store::default();
|
||||||
|
let first_ptr;
|
||||||
|
{
|
||||||
|
let owner = store.owner();
|
||||||
|
first_ptr = owner.insert(1).raw.data.as_ptr();
|
||||||
|
drop(owner);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let owner = store.owner();
|
||||||
|
let second_ptr = owner.insert(1234).raw.data.as_ptr();
|
||||||
|
assert_eq!(first_ptr, second_ptr);
|
||||||
|
drop(owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn leaking_is_ok() {
|
||||||
|
let data = String::from("hello world");
|
||||||
|
let store = Store::default();
|
||||||
|
let key;
|
||||||
|
{
|
||||||
|
// create an owner
|
||||||
|
let owner = store.owner();
|
||||||
|
// insert data into the store
|
||||||
|
key = owner.insert(data);
|
||||||
|
// don't drop the owner
|
||||||
|
std::mem::forget(owner);
|
||||||
|
}
|
||||||
|
assert_eq!(key.try_read().as_deref(), Some(&"hello world".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drops() {
|
||||||
|
let data = String::from("hello world");
|
||||||
|
let store = Store::default();
|
||||||
|
let key;
|
||||||
|
{
|
||||||
|
// create an owner
|
||||||
|
let owner = store.owner();
|
||||||
|
// insert data into the store
|
||||||
|
key = owner.insert(data);
|
||||||
|
// drop the owner
|
||||||
|
}
|
||||||
|
assert!(key.try_read().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn works() {
|
||||||
|
let store = Store::default();
|
||||||
|
let owner = store.owner();
|
||||||
|
let key = owner.insert(1);
|
||||||
|
|
||||||
|
assert_eq!(*key.read(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_while_reading() {
|
||||||
|
let store = Store::default();
|
||||||
|
let owner = store.owner();
|
||||||
|
let key;
|
||||||
|
{
|
||||||
|
let data: String = "hello world".to_string();
|
||||||
|
key = owner.insert(data);
|
||||||
|
}
|
||||||
|
let value = key.read();
|
||||||
|
owner.insert(&1);
|
||||||
|
assert_eq!(*value, "hello world");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn panics() {
|
||||||
|
let store = Store::default();
|
||||||
|
let owner = store.owner();
|
||||||
|
let key = owner.insert(1);
|
||||||
|
drop(owner);
|
||||||
|
|
||||||
|
assert_eq!(*key.read(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzz() {
|
||||||
|
fn maybe_owner_scope(
|
||||||
|
store: &Store,
|
||||||
|
valid_keys: &mut Vec<CopyHandle<String>>,
|
||||||
|
invalid_keys: &mut Vec<CopyHandle<String>>,
|
||||||
|
path: &mut Vec<u8>,
|
||||||
|
) {
|
||||||
|
let branch_cutoff = 5;
|
||||||
|
let children = if path.len() < branch_cutoff {
|
||||||
|
rand::random::<u8>() % 4
|
||||||
|
} else {
|
||||||
|
rand::random::<u8>() % 2
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 0..children {
|
||||||
|
let owner = store.owner();
|
||||||
|
let key = owner.insert(format!("hello world {path:?}"));
|
||||||
|
valid_keys.push(key);
|
||||||
|
path.push(i);
|
||||||
|
// read all keys
|
||||||
|
println!("{:?}", path);
|
||||||
|
for key in valid_keys.iter() {
|
||||||
|
let value = key.read();
|
||||||
|
println!("{:?}", value);
|
||||||
|
assert!(value.starts_with("hello world"));
|
||||||
|
}
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
for key in invalid_keys.iter() {
|
||||||
|
assert!(!key.validate());
|
||||||
|
}
|
||||||
|
maybe_owner_scope(store, valid_keys, invalid_keys, path);
|
||||||
|
invalid_keys.push(valid_keys.pop().unwrap());
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..10 {
|
||||||
|
let store = Store::default();
|
||||||
|
maybe_owner_scope(&store, &mut Vec::new(), &mut Vec::new(), &mut Vec::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CopyHandle<T> {
|
||||||
|
raw: MemoryLocation,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
generation: u32,
|
||||||
|
_marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> CopyHandle<T> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn validate(&self) -> bool {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
self.raw.generation.get() == self.generation
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_read(&self) -> Option<Ref<'_, T>> {
|
||||||
|
self.validate()
|
||||||
|
.then(|| {
|
||||||
|
Ref::filter_map(self.raw.data.borrow(), |any| {
|
||||||
|
any.as_ref()?.downcast_ref::<T>()
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self) -> Ref<'_, T> {
|
||||||
|
self.try_read().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_write(&self) -> Option<RefMut<'_, T>> {
|
||||||
|
self.validate()
|
||||||
|
.then(|| {
|
||||||
|
RefMut::filter_map(self.raw.data.borrow_mut(), |any| {
|
||||||
|
any.as_mut()?.downcast_mut::<T>()
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&self) -> RefMut<'_, T> {
|
||||||
|
self.try_write().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Copy for CopyHandle<T> {}
|
||||||
|
|
||||||
|
impl<T> Clone for CopyHandle<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct MemoryLocation {
|
||||||
|
data: &'static RefCell<Option<Box<dyn std::any::Any>>>,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
generation: &'static Cell<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemoryLocation {
|
||||||
|
#[allow(unused)]
|
||||||
|
fn drop(&self) {
|
||||||
|
let old = self.data.borrow_mut().take();
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if old.is_some() {
|
||||||
|
let new_generation = self.generation.get() + 1;
|
||||||
|
self.generation.set(new_generation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace<T: 'static>(&mut self, value: T) -> CopyHandle<T> {
|
||||||
|
let mut inner_mut = self.data.borrow_mut();
|
||||||
|
|
||||||
|
let raw = Box::new(value);
|
||||||
|
let old = inner_mut.replace(raw);
|
||||||
|
assert!(old.is_none());
|
||||||
|
CopyHandle {
|
||||||
|
raw: *self,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
generation: self.generation.get(),
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Store {
|
||||||
|
bump: &'static Bump,
|
||||||
|
recycled: Rc<RefCell<Vec<MemoryLocation>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Store {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
bump: Box::leak(Box::new(Bump::new())),
|
||||||
|
recycled: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Store {
|
||||||
|
fn recycle(&self, location: MemoryLocation) {
|
||||||
|
location.drop();
|
||||||
|
self.recycled.borrow_mut().push(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn claim(&self) -> MemoryLocation {
|
||||||
|
if let Some(location) = self.recycled.borrow_mut().pop() {
|
||||||
|
location
|
||||||
|
} else {
|
||||||
|
let data: &'static RefCell<_> = self.bump.alloc(RefCell::new(None));
|
||||||
|
MemoryLocation {
|
||||||
|
data,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
generation: self.bump.alloc(Cell::new(0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn owner(&self) -> Owner {
|
||||||
|
Owner {
|
||||||
|
store: self.clone(),
|
||||||
|
owned: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Owner {
|
||||||
|
store: Store,
|
||||||
|
owned: Rc<RefCell<Vec<MemoryLocation>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Owner {
|
||||||
|
pub fn insert<T: 'static>(&self, value: T) -> CopyHandle<T> {
|
||||||
|
let mut location = self.store.claim();
|
||||||
|
let key = location.replace(value);
|
||||||
|
self.owned.borrow_mut().push(location);
|
||||||
|
key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Owner {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
for location in self.owned.borrow().iter() {
|
||||||
|
self.store.recycle(*location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
-56
@@ -1,70 +1,65 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefMut},
|
cell::{Ref, RefMut},
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
marker::PhantomData,
|
|
||||||
ops::{Add, Div, Mul, Sub},
|
ops::{Add, Div, Mul, Sub},
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod copy;
|
||||||
mod rt;
|
mod rt;
|
||||||
|
|
||||||
use dioxus_core::ScopeState;
|
|
||||||
pub use rt::*;
|
pub use rt::*;
|
||||||
|
|
||||||
pub fn use_init_signal_rt(cx: &ScopeState) {
|
use dioxus_core::{
|
||||||
cx.use_hook(|| {
|
prelude::{current_scope_id, schedule_update_any},
|
||||||
let rt = claim_rt(cx.schedule_update_any());
|
ScopeId, ScopeState,
|
||||||
cx.provide_context(rt);
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
|
pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
|
||||||
cx.use_hook(|| {
|
*cx.use_hook(|| Signal::new(f()))
|
||||||
let rt: &'static SignalRt = match cx.consume_context() {
|
|
||||||
Some(rt) => rt,
|
|
||||||
None => cx.provide_context(claim_rt(cx.schedule_update_any())),
|
|
||||||
};
|
|
||||||
|
|
||||||
let id = rt.init(f());
|
|
||||||
rt.subscribe(id, cx.scope_id());
|
|
||||||
|
|
||||||
struct SignalHook<T> {
|
|
||||||
signal: Signal<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Drop for SignalHook<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.signal.rt.remove(self.signal.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SignalHook {
|
|
||||||
signal: Signal {
|
|
||||||
id,
|
|
||||||
rt,
|
|
||||||
t: PhantomData,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.signal
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Signal<T> {
|
struct SignalData<T> {
|
||||||
id: usize,
|
subscribers: Vec<ScopeId>,
|
||||||
rt: &'static SignalRt,
|
update_any: Arc<dyn Fn(ScopeId)>,
|
||||||
t: PhantomData<T>,
|
value: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Signal<T: 'static> {
|
||||||
|
inner: CopyValue<SignalData<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> Signal<T> {
|
impl<T: 'static> Signal<T> {
|
||||||
|
pub fn new(value: T) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: CopyValue::new(SignalData {
|
||||||
|
subscribers: Vec::new(),
|
||||||
|
update_any: schedule_update_any().expect("in a virtual dom"),
|
||||||
|
value,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read(&self) -> Ref<T> {
|
pub fn read(&self) -> Ref<T> {
|
||||||
self.rt.read(self.id)
|
if let Some(current_scope_id) = current_scope_id() {
|
||||||
|
let mut inner = self.inner.write();
|
||||||
|
if !inner.subscribers.contains(¤t_scope_id) {
|
||||||
|
inner.subscribers.push(current_scope_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ref::map(self.inner.read(), |v| &v.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&self) -> RefMut<T> {
|
pub fn write(&self) -> RefMut<T> {
|
||||||
self.rt.write(self.id)
|
let inner = self.inner.write();
|
||||||
|
for &scope_id in &inner.subscribers {
|
||||||
|
(inner.update_any)(scope_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
RefMut::map(inner, |v| &mut v.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&mut self, value: T) {
|
pub fn set(&mut self, value: T) {
|
||||||
self.rt.set(self.id, value);
|
*self.write() = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
||||||
@@ -72,23 +67,15 @@ impl<T: 'static> Signal<T> {
|
|||||||
f(&*write)
|
f(&*write)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update<O>(&self, _f: impl FnOnce(&mut T) -> O) -> O {
|
pub fn update<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
|
||||||
let mut write = self.write();
|
let mut write = self.write();
|
||||||
_f(&mut *write)
|
f(&mut *write)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone + 'static> Signal<T> {
|
impl<T: Clone + 'static> Signal<T> {
|
||||||
pub fn get(&self) -> T {
|
pub fn get(&self) -> T {
|
||||||
self.rt.get(self.id)
|
self.read().clone()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Clone + 'static> std::ops::Deref for Signal<T> {
|
|
||||||
type Target = dyn Fn() -> T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.rt.getter(self.id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +89,7 @@ impl<T> Copy for Signal<T> {}
|
|||||||
|
|
||||||
impl<T: Display + 'static> Display for Signal<T> {
|
impl<T: Display + 'static> Display for Signal<T> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
self.rt.with::<T, _>(self.id, |v| T::fmt(v, f))
|
self.with(|v| Display::fmt(v, f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+42
-98
@@ -1,121 +1,65 @@
|
|||||||
use std::{any::Any, cell::RefCell, sync::Arc};
|
use std::cell::{Ref, RefMut};
|
||||||
|
|
||||||
use dioxus_core::ScopeId;
|
use std::rc::Rc;
|
||||||
use slab::Slab;
|
|
||||||
|
|
||||||
thread_local! {
|
use dioxus_core::prelude::{consume_context, provide_root_context};
|
||||||
// we cannot drop these since any future might be using them
|
|
||||||
static RUNTIMES: RefCell<Vec<&'static SignalRt>> = RefCell::new(Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provide the runtime for signals
|
use crate::copy::{CopyHandle, Owner, Store};
|
||||||
///
|
|
||||||
/// This will reuse dead runtimes
|
fn current_store() -> Store {
|
||||||
pub fn claim_rt(update_any: Arc<dyn Fn(ScopeId)>) -> &'static SignalRt {
|
match consume_context() {
|
||||||
RUNTIMES.with(|runtimes| {
|
Some(rt) => rt,
|
||||||
if let Some(rt) = runtimes.borrow_mut().pop() {
|
None => {
|
||||||
return rt;
|
let store = Store::default();
|
||||||
|
provide_root_context(store).expect("in a virtual dom")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Box::leak(Box::new(SignalRt {
|
|
||||||
signals: RefCell::new(Slab::new()),
|
|
||||||
update_any,
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push this runtime into the global runtime list
|
fn current_owner() -> Rc<Owner> {
|
||||||
pub fn reclam_rt(_rt: &'static SignalRt) {
|
match consume_context() {
|
||||||
RUNTIMES.with(|runtimes| {
|
Some(rt) => rt,
|
||||||
runtimes.borrow_mut().push(_rt);
|
None => {
|
||||||
});
|
let owner = Rc::new(current_store().owner());
|
||||||
|
provide_root_context(owner).expect("in a virtual dom")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SignalRt {
|
impl<T> Copy for CopyValue<T> {}
|
||||||
pub(crate) signals: RefCell<Slab<Inner>>,
|
|
||||||
pub(crate) update_any: Arc<dyn Fn(ScopeId)>,
|
impl<T> Clone for CopyValue<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignalRt {
|
pub struct CopyValue<T: 'static> {
|
||||||
pub fn init<T: 'static>(&'static self, val: T) -> usize {
|
pub value: CopyHandle<T>,
|
||||||
self.signals.borrow_mut().insert(Inner {
|
}
|
||||||
value: Box::new(val),
|
|
||||||
subscribers: Vec::new(),
|
|
||||||
getter: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subscribe(&self, id: usize, subscriber: ScopeId) {
|
impl<T: 'static> CopyValue<T> {
|
||||||
self.signals.borrow_mut()[id].subscribers.push(subscriber);
|
pub fn new(value: T) -> Self {
|
||||||
}
|
let owner = current_owner();
|
||||||
|
|
||||||
pub fn get<T: Clone + 'static>(&self, id: usize) -> T {
|
Self {
|
||||||
self.signals.borrow()[id]
|
value: owner.insert(value),
|
||||||
.value
|
|
||||||
.downcast_ref::<T>()
|
|
||||||
.cloned()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set<T: 'static>(&self, id: usize, value: T) {
|
|
||||||
let mut signals = self.signals.borrow_mut();
|
|
||||||
let inner = &mut signals[id];
|
|
||||||
inner.value = Box::new(value);
|
|
||||||
|
|
||||||
for subscriber in inner.subscribers.iter() {
|
|
||||||
(self.update_any)(*subscriber);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&self, id: usize) {
|
pub fn try_read(&self) -> Option<Ref<'_, T>> {
|
||||||
self.signals.borrow_mut().remove(id);
|
self.value.try_read()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with<T: 'static, O>(&self, id: usize, f: impl FnOnce(&T) -> O) -> O {
|
pub fn read(&self) -> Ref<'_, T> {
|
||||||
let signals = self.signals.borrow();
|
self.value.read()
|
||||||
let inner = &signals[id];
|
|
||||||
let inner = inner.value.downcast_ref::<T>().unwrap();
|
|
||||||
f(inner)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn read<T: 'static>(&self, id: usize) -> std::cell::Ref<T> {
|
pub fn try_write(&self) -> Option<RefMut<'_, T>> {
|
||||||
let signals = self.signals.borrow();
|
self.value.try_write()
|
||||||
std::cell::Ref::map(signals, |signals| {
|
|
||||||
signals[id].value.downcast_ref::<T>().unwrap()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn write<T: 'static>(&self, id: usize) -> std::cell::RefMut<T> {
|
pub fn write(&self) -> RefMut<'_, T> {
|
||||||
let signals = self.signals.borrow_mut();
|
self.value.write()
|
||||||
std::cell::RefMut::map(signals, |signals| {
|
|
||||||
signals[id].value.downcast_mut::<T>().unwrap()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn getter<T: 'static + Clone>(&self, id: usize) -> &dyn Fn() -> T {
|
|
||||||
let mut signals = self.signals.borrow_mut();
|
|
||||||
let inner = &mut signals[id];
|
|
||||||
let r = inner.getter.as_mut();
|
|
||||||
|
|
||||||
if r.is_none() {
|
|
||||||
let rt = self;
|
|
||||||
let r = move || rt.get::<T>(id);
|
|
||||||
let getter: Box<dyn Fn() -> T> = Box::new(r);
|
|
||||||
let getter: Box<dyn Fn()> = unsafe { std::mem::transmute(getter) };
|
|
||||||
|
|
||||||
inner.getter = Some(getter);
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = inner.getter.as_ref().unwrap();
|
|
||||||
|
|
||||||
unsafe { std::mem::transmute::<&dyn Fn(), &dyn Fn() -> T>(r) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Inner {
|
|
||||||
pub value: Box<dyn Any>,
|
|
||||||
pub subscribers: Vec<ScopeId>,
|
|
||||||
|
|
||||||
// todo: this has a soundness hole in it that you might not run into
|
|
||||||
pub getter: Option<Box<dyn Fn()>>,
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user