From 04cdb14e5bbe624a4f083ec9d6a805677f1c7634 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 4 Aug 2023 13:53:05 -0700 Subject: [PATCH] improve signal copy runtime --- packages/core/src/arena.rs | 7 +- packages/core/src/create.rs | 6 +- packages/core/src/diff.rs | 37 +++- packages/core/src/lib.rs | 11 +- packages/core/src/runtime.rs | 87 ++++++++ packages/core/src/scheduler/wait.rs | 6 +- packages/core/src/scope_arena.rs | 47 ++--- packages/core/src/scope_context.rs | 288 ++++++++++++++++++++++++++ packages/core/src/scopes.rs | 131 +++--------- packages/core/src/virtual_dom.rs | 36 ++-- packages/signals/Cargo.toml | 1 + packages/signals/src/copy.rs | 301 ++++++++++++++++++++++++++++ packages/signals/src/lib.rs | 99 ++++----- packages/signals/src/rt.rs | 140 ++++--------- 14 files changed, 878 insertions(+), 319 deletions(-) create mode 100644 packages/core/src/runtime.rs create mode 100644 packages/core/src/scope_context.rs create mode 100644 packages/signals/src/copy.rs diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index b6254136d..25b040643 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -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); diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 6159c882b..f332760a5 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -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 = 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()) } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 7865224aa..da1716e70 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -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"), } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 565ae12be..b65eaa86a 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -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, }; } diff --git a/packages/core/src/runtime.rs b/packages/core/src/runtime.rs new file mode 100644 index 000000000..7a9921e99 --- /dev/null +++ b/packages/core/src/runtime.rs @@ -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> = 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: F) -> Option +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: F) -> Option +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, + pub(crate) scheduler: Rc, + + // While diffing we need some sort of way of breaking off a stream of suspended mutations. + pub(crate) scope_stack: RefCell>, +} + +impl Runtime { + pub(crate) fn new(scheduler: Rc) -> Rc { + Rc::new(Self { + scheduler, + + scope_contexts: Default::default(), + + scope_stack: Default::default(), + }) + } + + /// Get the current scope id + pub fn current_scope_id(&self) -> Option { + 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() + } +} diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index b475c6751..a04bf2730 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -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); diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index b298a8996..1a64612bf 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -13,35 +13,29 @@ impl VirtualDom { props: Box>, 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 diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs new file mode 100644 index 000000000..e1a64bc2b --- /dev/null +++ b/packages/core/src/scope_context.rs @@ -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, + + pub(crate) height: u32, + pub(crate) suspended: Cell, + + pub(crate) shared_contexts: RefCell)>>, + + pub(crate) tasks: Rc, + pub(crate) spawned_tasks: RefCell>, +} + +impl ScopeContext { + pub fn name(&self) -> &str { + self.name + } + + pub fn height(&self) -> u32 { + self.height + } + + pub fn parent_id(&self) -> Option { + 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 { + 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 { + 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(&self) -> Option { + self.shared_contexts + .borrow() + .iter() + .find(|(k, _)| *k == TypeId::of::()) + .map(|(_, v)| v)? + .downcast_ref::() + .cloned() + } + + /// Try to retrieve a shared state with type `T` from any parent scope. + /// + /// Clones the state if it exists. + pub fn consume_context(&self) -> Option { + 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::()) + { + result = shared.1.downcast_ref::().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::(); + /// render!(div { "hello {state.0}" }) + /// } + /// ``` + pub fn provide_context(&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::() { + std::mem::swap(ctx, &mut value.clone()); + return value; + } + } + + // Else, just push it + contexts.push((TypeId::of::(), 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(&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 + '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 + '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 + '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::>() { + 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 { + 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> { + with_current_scope(|cx| cx.schedule_update_any()) +} + +/// Get the current scope id +pub fn current_scope_id() -> Option { + with_runtime(|rt| rt.current_scope_id()).flatten() +} + +/// Consume context from the current scope +pub fn consume_context() -> Option { + with_current_scope(|cx| cx.consume_context::()).flatten() +} + +/// Check if the current scope has a context +pub fn has_context() -> Option { + with_current_scope(|cx| cx.has_context::()).flatten() +} + +/// Provide context to the current scope +pub fn provide_context(value: T) -> Option { + with_current_scope(|cx| cx.provide_context(value)) +} + +/// Provide a context to the root scope +pub fn provide_root_context(value: T) -> Option { + with_current_scope(|cx| cx.provide_root_context(value)) +} + +/// Suspends the current component +pub fn suspend() -> Option> { + 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() +} diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 9470c3d0d..d1897331a 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -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, + pub(crate) context_id: ScopeId, + pub(crate) render_cnt: Cell, - 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, - pub(crate) hooks: RefCell>>>, pub(crate) hook_idx: Cell, - pub(crate) shared_contexts: RefCell)>>, - - pub(crate) tasks: Rc, - pub(crate) spawned_tasks: RefCell>, - pub(crate) borrowed_props: RefCell>>, pub(crate) attributes_to_drop: RefCell>>, @@ -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 { + pub fn parent_id(&self) -> Option { // 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 { - 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 { - 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(&self) -> Option { - self.shared_contexts - .borrow() - .iter() - .find(|(k, _)| *k == TypeId::of::()) - .map(|(_, v)| v)? - .downcast_ref::() - .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(&self) -> Option { - 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::()) - { - return shared.1.downcast_ref::().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(&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::() { - std::mem::swap(ctx, &mut value.clone()); - return value; - } - } - - // Else, just push it - contexts.push((TypeId::of::(), 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(&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 + '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 + '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 + '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) -> Element; /// async fn(Scope>) -> Element; /// ``` - pub fn component

( + pub fn component( &'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 { - self.suspended.set(true); - None + self.context().suspend() } /// Store a value between renders. The foundational hook for all other hooks. diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index dce5415f6..0d22cbeea 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -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, + + pub(crate) dirty_scopes: BTreeSet, + // Maps a template path to a map of byteindexes to templates pub(crate) templates: FxHashMap>>, - pub(crate) scopes: Slab>, - pub(crate) dirty_scopes: BTreeSet, - pub(crate) scheduler: Rc, // 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, - // While diffing we need some sort of way of breaking off a stream of suspended mutations. - pub(crate) scope_stack: Vec, + pub(crate) mutations: Mutations<'static>, + + pub(crate) runtime: Rc, // Currently suspended scopes pub(crate) suspended_scopes: FxHashSet, pub(crate) rx: futures_channel::mpsc::UnboundedReceiver, - - pub(crate) mutations: Mutations<'static>, } impl VirtualDom { @@ -251,16 +252,16 @@ impl VirtualDom { /// ``` pub fn new_with_props(root: fn(Scope

) -> 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, }); } } diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index a0c3534b0..bfa16363b 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -9,3 +9,4 @@ edition = "2018" [dependencies] dioxus-core = { workspace = true } slab = { workspace = true } +bumpalo = { version = "3.6" } diff --git a/packages/signals/src/copy.rs b/packages/signals/src/copy.rs new file mode 100644 index 000000000..a10540429 --- /dev/null +++ b/packages/signals/src/copy.rs @@ -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>, + invalid_keys: &mut Vec>, + path: &mut Vec, + ) { + let branch_cutoff = 5; + let children = if path.len() < branch_cutoff { + rand::random::() % 4 + } else { + rand::random::() % 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 { + raw: MemoryLocation, + #[cfg(debug_assertions)] + generation: u32, + _marker: PhantomData, +} + +impl CopyHandle { + #[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> { + self.validate() + .then(|| { + Ref::filter_map(self.raw.data.borrow(), |any| { + any.as_ref()?.downcast_ref::() + }) + .ok() + }) + .flatten() + } + + pub fn read(&self) -> Ref<'_, T> { + self.try_read().unwrap() + } + + pub fn try_write(&self) -> Option> { + self.validate() + .then(|| { + RefMut::filter_map(self.raw.data.borrow_mut(), |any| { + any.as_mut()?.downcast_mut::() + }) + .ok() + }) + .flatten() + } + + pub fn write(&self) -> RefMut<'_, T> { + self.try_write().unwrap() + } +} + +impl Copy for CopyHandle {} + +impl Clone for CopyHandle { + fn clone(&self) -> Self { + *self + } +} + +#[derive(Clone, Copy)] +struct MemoryLocation { + data: &'static RefCell>>, + #[cfg(debug_assertions)] + generation: &'static Cell, +} + +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(&mut self, value: T) -> CopyHandle { + 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>>, +} + +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>>, +} + +impl Owner { + pub fn insert(&self, value: T) -> CopyHandle { + 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) + } + } +} diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index 5b6a740cf..803d07a61 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -1,70 +1,65 @@ use std::{ cell::{Ref, RefMut}, fmt::Display, - marker::PhantomData, ops::{Add, Div, Mul, Sub}, + sync::Arc, }; +mod copy; mod rt; - -use dioxus_core::ScopeState; pub use rt::*; -pub fn use_init_signal_rt(cx: &ScopeState) { - cx.use_hook(|| { - let rt = claim_rt(cx.schedule_update_any()); - cx.provide_context(rt); - }); -} +use dioxus_core::{ + prelude::{current_scope_id, schedule_update_any}, + ScopeId, ScopeState, +}; pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal { - cx.use_hook(|| { - 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 { - signal: Signal, - } - - impl Drop for SignalHook { - fn drop(&mut self) { - self.signal.rt.remove(self.signal.id); - } - } - - SignalHook { - signal: Signal { - id, - rt, - t: PhantomData, - }, - } - }) - .signal + *cx.use_hook(|| Signal::new(f())) } -pub struct Signal { - id: usize, - rt: &'static SignalRt, - t: PhantomData, +struct SignalData { + subscribers: Vec, + update_any: Arc, + value: T, +} + +pub struct Signal { + inner: CopyValue>, } impl Signal { + 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 { - 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 { - 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) { - self.rt.set(self.id, value); + *self.write() = value; } pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { @@ -72,23 +67,15 @@ impl Signal { f(&*write) } - pub fn update(&self, _f: impl FnOnce(&mut T) -> O) -> O { + pub fn update(&self, f: impl FnOnce(&mut T) -> O) -> O { let mut write = self.write(); - _f(&mut *write) + f(&mut *write) } } impl Signal { pub fn get(&self) -> T { - self.rt.get(self.id) - } -} - -impl std::ops::Deref for Signal { - type Target = dyn Fn() -> T; - - fn deref(&self) -> &Self::Target { - self.rt.getter(self.id) + self.read().clone() } } @@ -102,7 +89,7 @@ impl Copy for Signal {} impl Display for Signal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.rt.with::(self.id, |v| T::fmt(v, f)) + self.with(|v| Display::fmt(v, f)) } } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index e849f9008..def6666b1 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -1,121 +1,65 @@ -use std::{any::Any, cell::RefCell, sync::Arc}; +use std::cell::{Ref, RefMut}; -use dioxus_core::ScopeId; -use slab::Slab; +use std::rc::Rc; -thread_local! { - // we cannot drop these since any future might be using them - static RUNTIMES: RefCell> = RefCell::new(Vec::new()); -} +use dioxus_core::prelude::{consume_context, provide_root_context}; -/// Provide the runtime for signals -/// -/// This will reuse dead runtimes -pub fn claim_rt(update_any: Arc) -> &'static SignalRt { - RUNTIMES.with(|runtimes| { - if let Some(rt) = runtimes.borrow_mut().pop() { - return rt; +use crate::copy::{CopyHandle, Owner, Store}; + +fn current_store() -> Store { + match consume_context() { + Some(rt) => rt, + None => { + 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 -pub fn reclam_rt(_rt: &'static SignalRt) { - RUNTIMES.with(|runtimes| { - runtimes.borrow_mut().push(_rt); - }); +fn current_owner() -> Rc { + match consume_context() { + Some(rt) => rt, + None => { + let owner = Rc::new(current_store().owner()); + provide_root_context(owner).expect("in a virtual dom") + } + } } -pub struct SignalRt { - pub(crate) signals: RefCell>, - pub(crate) update_any: Arc, +impl Copy for CopyValue {} + +impl Clone for CopyValue { + fn clone(&self) -> Self { + *self + } } -impl SignalRt { - pub fn init(&'static self, val: T) -> usize { - self.signals.borrow_mut().insert(Inner { - value: Box::new(val), - subscribers: Vec::new(), - getter: None, - }) - } +pub struct CopyValue { + pub value: CopyHandle, +} - pub fn subscribe(&self, id: usize, subscriber: ScopeId) { - self.signals.borrow_mut()[id].subscribers.push(subscriber); - } +impl CopyValue { + pub fn new(value: T) -> Self { + let owner = current_owner(); - pub fn get(&self, id: usize) -> T { - self.signals.borrow()[id] - .value - .downcast_ref::() - .cloned() - .unwrap() - } - - pub fn set(&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); + Self { + value: owner.insert(value), } } - pub fn remove(&self, id: usize) { - self.signals.borrow_mut().remove(id); + pub fn try_read(&self) -> Option> { + self.value.try_read() } - pub fn with(&self, id: usize, f: impl FnOnce(&T) -> O) -> O { - let signals = self.signals.borrow(); - let inner = &signals[id]; - let inner = inner.value.downcast_ref::().unwrap(); - f(inner) + pub fn read(&self) -> Ref<'_, T> { + self.value.read() } - pub(crate) fn read(&self, id: usize) -> std::cell::Ref { - let signals = self.signals.borrow(); - std::cell::Ref::map(signals, |signals| { - signals[id].value.downcast_ref::().unwrap() - }) + pub fn try_write(&self) -> Option> { + self.value.try_write() } - pub(crate) fn write(&self, id: usize) -> std::cell::RefMut { - let signals = self.signals.borrow_mut(); - std::cell::RefMut::map(signals, |signals| { - signals[id].value.downcast_mut::().unwrap() - }) - } - - pub(crate) fn getter(&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::(id); - let getter: Box T> = Box::new(r); - let getter: Box = 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 fn write(&self) -> RefMut<'_, T> { + self.value.write() } } - -pub(crate) struct Inner { - pub value: Box, - pub subscribers: Vec, - - // todo: this has a soundness hole in it that you might not run into - pub getter: Option>, -}