Browse Source

Merge pull request #1300 from Demonthos/signals

Complete Signals implementation
Jonathan Kelley 1 year ago
parent
commit
c95f70f55a
41 changed files with 2827 additions and 409 deletions
  1. 2 0
      Cargo.toml
  2. 1 7
      examples/clock.rs
  3. 1 1
      examples/signals.rs
  4. 13 4
      packages/core/src/arena.rs
  5. 5 5
      packages/core/src/create.rs
  6. 26 9
      packages/core/src/diff.rs
  7. 9 0
      packages/core/src/events.rs
  8. 14 7
      packages/core/src/lib.rs
  9. 108 0
      packages/core/src/runtime.rs
  10. 13 4
      packages/core/src/scheduler/wait.rs
  11. 33 26
      packages/core/src/scope_arena.rs
  12. 335 0
      packages/core/src/scope_context.rs
  13. 40 100
      packages/core/src/scopes.rs
  14. 38 20
      packages/core/src/virtual_dom.rs
  15. 7 2
      packages/desktop/src/protocol.rs
  16. 7 1
      packages/dioxus-tui/examples/hover.rs
  17. 17 0
      packages/generational-box/Cargo.toml
  18. 34 0
      packages/generational-box/README.md
  19. 359 0
      packages/generational-box/src/lib.rs
  20. 2 0
      packages/hooks/src/computed.rs
  21. 1 2
      packages/hooks/src/lib.rs
  22. 1 1
      packages/html/src/eval.rs
  23. 0 1
      packages/native-core/src/real_dom.rs
  24. 2 8
      packages/rsx/src/lib.rs
  25. 13 1
      packages/signals/Cargo.toml
  26. 122 0
      packages/signals/README.md
  27. 25 0
      packages/signals/examples/context.rs
  28. 38 0
      packages/signals/examples/dependancies.rs
  29. 30 0
      packages/signals/examples/selector.rs
  30. 150 0
      packages/signals/examples/split_subscriptions.rs
  31. 67 0
      packages/signals/src/dependency.rs
  32. 96 0
      packages/signals/src/effect.rs
  33. 294 0
      packages/signals/src/impls.rs
  34. 11 128
      packages/signals/src/lib.rs
  35. 120 82
      packages/signals/src/rt.rs
  36. 105 0
      packages/signals/src/selector.rs
  37. 344 0
      packages/signals/src/signal.rs
  38. 60 0
      packages/signals/tests/create.rs
  39. 47 0
      packages/signals/tests/effect.rs
  40. 145 0
      packages/signals/tests/selector.rs
  41. 92 0
      packages/signals/tests/subscribe.rs

+ 2 - 0
Cargo.toml

@@ -25,6 +25,7 @@ members = [
     "packages/native-core",
     "packages/native-core",
     "packages/native-core-macro",
     "packages/native-core-macro",
     "packages/rsx-rosetta",
     "packages/rsx-rosetta",
+    "packages/generational-box",
     "packages/signals",
     "packages/signals",
     "packages/hot-reload",
     "packages/hot-reload",
     "packages/fullstack",
     "packages/fullstack",
@@ -76,6 +77,7 @@ dioxus-native-core = { path = "packages/native-core", version = "0.4.0" }
 dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" }
 dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" }
 rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
 rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
 dioxus-signals = { path = "packages/signals" }
 dioxus-signals = { path = "packages/signals" }
+generational-box = { path = "packages/generational-box" }
 dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" }
 dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" }
 dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1"  }
 dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1"  }
 dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" }
 dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" }

+ 1 - 7
examples/clock.rs

@@ -1,17 +1,11 @@
-//! Example: README.md showcase
-//!
-//! The example from the README.md.
-
 use dioxus::prelude::*;
 use dioxus::prelude::*;
-use dioxus_signals::{use_init_signal_rt, use_signal};
+use dioxus_signals::use_signal;
 
 
 fn main() {
 fn main() {
     dioxus_desktop::launch(app);
     dioxus_desktop::launch(app);
 }
 }
 
 
 fn app(cx: Scope) -> Element {
 fn app(cx: Scope) -> Element {
-    use_init_signal_rt(cx);
-
     let mut count = use_signal(cx, || 0);
     let mut count = use_signal(cx, || 0);
 
 
     use_future!(cx, || async move {
     use_future!(cx, || async move {

+ 1 - 1
examples/signals.rs

@@ -20,7 +20,7 @@ fn app(cx: Scope) -> Element {
         button { onclick: move |_| count += 1, "Up high!" }
         button { onclick: move |_| count += 1, "Up high!" }
         button { onclick: move |_| count -= 1, "Down low!" }
         button { onclick: move |_| count -= 1, "Down low!" }
 
 
-        if count() > 5 {
+        if count.value() > 5 {
             rsx!{ h2 { "High five!" } }
             rsx!{ h2 { "High five!" } }
         }
         }
     })
     })

+ 13 - 4
packages/core/src/arena.rs

@@ -19,6 +19,9 @@ pub(crate) struct ElementRef {
 
 
     // The actual template
     // The actual template
     pub template: Option<NonNull<VNode<'static>>>,
     pub template: Option<NonNull<VNode<'static>>>,
+
+    // The scope the element belongs to
+    pub scope: ScopeId,
 }
 }
 
 
 #[derive(Clone, Copy, Debug)]
 #[derive(Clone, Copy, Debug)]
@@ -32,6 +35,7 @@ impl ElementRef {
         Self {
         Self {
             template: None,
             template: None,
             path: ElementPath::Root(0),
             path: ElementPath::Root(0),
+            scope: ScopeId(0),
         }
         }
     }
     }
 }
 }
@@ -56,11 +60,13 @@ impl VirtualDom {
     fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId {
     fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId {
         let entry = self.elements.vacant_entry();
         let entry = self.elements.vacant_entry();
         let id = entry.key();
         let id = entry.key();
+        let scope = self.runtime.current_scope_id().unwrap_or(ScopeId(0));
 
 
         entry.insert(ElementRef {
         entry.insert(ElementRef {
             // We know this is non-null because it comes from a reference
             // We know this is non-null because it comes from a reference
             template: Some(unsafe { NonNull::new_unchecked(template as *const _ as *mut _) }),
             template: Some(unsafe { NonNull::new_unchecked(template as *const _ as *mut _) }),
             path,
             path,
+            scope,
         });
         });
         ElementId(id)
         ElementId(id)
     }
     }
@@ -91,7 +97,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 +116,13 @@ 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
-        for task_id in scope.spawned_tasks.borrow_mut().drain() {
-            scope.tasks.remove(task_id);
+            // Drop all the futures once the hooks are dropped
+            for task_id in context.spawned_tasks.borrow_mut().drain() {
+                context.tasks.remove(task_id);
+            }
         }
         }
 
 
         self.scopes.remove(id.0);
         self.scopes.remove(id.0);

+ 5 - 5
packages/core/src/create.rs

@@ -66,10 +66,10 @@ 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);
-        let out = self.create(template);
-        self.scope_stack.pop();
-        out
+        self.runtime.scope_stack.borrow_mut().push(scope);
+        let nodes = self.create(template);
+        self.runtime.scope_stack.borrow_mut().pop();
+        nodes
     }
     }
 
 
     /// Create this template and write its mutations
     /// Create this template and write its mutations
@@ -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())
     }
     }

+ 26 - 9
packages/core/src/diff.rs

@@ -15,9 +15,7 @@ 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.scope_stack.push(scope);
+        let scope_state = &mut self.get_scope(scope).unwrap();
         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 +45,6 @@ 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();
     }
     }
 
 
     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 +207,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.get_context(scope_id).unwrap().height,
             id: scope_id,
             id: scope_id,
         });
         });
     }
     }
@@ -714,7 +711,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 +917,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 +943,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 +964,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"),
                 }
                 }

+ 9 - 0
packages/core/src/events.rs

@@ -1,3 +1,4 @@
+use crate::{runtime::with_runtime, ScopeId};
 use std::{
 use std::{
     cell::{Cell, RefCell},
     cell::{Cell, RefCell},
     rc::Rc,
     rc::Rc,
@@ -135,12 +136,14 @@ impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
 ///
 ///
 /// ```
 /// ```
 pub struct EventHandler<'bump, T = ()> {
 pub struct EventHandler<'bump, T = ()> {
+    pub(crate) origin: ScopeId,
     pub(super) callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
     pub(super) callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
 }
 }
 
 
 impl<T> Default for EventHandler<'_, T> {
 impl<T> Default for EventHandler<'_, T> {
     fn default() -> Self {
     fn default() -> Self {
         Self {
         Self {
+            origin: ScopeId(0),
             callback: Default::default(),
             callback: Default::default(),
         }
         }
     }
     }
@@ -154,7 +157,13 @@ impl<T> EventHandler<'_, T> {
     /// This borrows the event using a RefCell. Recursively calling a listener will cause a panic.
     /// This borrows the event using a RefCell. Recursively calling a listener will cause a panic.
     pub fn call(&self, event: T) {
     pub fn call(&self, event: T) {
         if let Some(callback) = self.callback.borrow_mut().as_mut() {
         if let Some(callback) = self.callback.borrow_mut().as_mut() {
+            with_runtime(|rt| {
+                rt.scope_stack.borrow_mut().push(self.origin);
+            });
             callback(event);
             callback(event);
+            with_runtime(|rt| {
+                rt.scope_stack.borrow_mut().pop();
+            });
         }
         }
     }
     }
 
 

+ 14 - 7
packages/core/src/lib.rs

@@ -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::*;
 
 
@@ -70,10 +73,11 @@ pub(crate) mod innerlude {
 }
 }
 
 
 pub use crate::innerlude::{
 pub use crate::innerlude::{
-    fc_to_builder, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, CapturedError,
-    Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation,
-    Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, TaskId, Template,
-    TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom,
+    fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue,
+    CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode,
+    LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped,
+    TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText,
+    VirtualDom,
 };
 };
 
 
 /// The purpose of this module is to alleviate imports of many common types
 /// The purpose of this module is to alleviate imports of many common types
@@ -81,9 +85,12 @@ 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,
-        IntoAttributeValue, LazyNodes, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId,
-        Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
+        consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context,
+        provide_context, provide_context_to_scope, provide_root_context, push_future,
+        remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue,
+        Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, LazyNodes,
+        Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute,
+        TemplateNode, Throw, VNode, VirtualDom,
     };
     };
 }
 }
 
 

+ 108 - 0
packages/core/src/runtime.rs

@@ -0,0 +1,108 @@
+use std::cell::{Cell, Ref, RefCell};
+
+use crate::{innerlude::Scheduler, scope_context::ScopeContext, scopes::ScopeId};
+use std::rc::Rc;
+
+thread_local! {
+    static RUNTIMES: RefCell<Vec<Rc<Runtime>>> = RefCell::new(vec![]);
+}
+
+/// Pushes a new scope onto the stack
+pub(crate) fn push_runtime(runtime: Rc<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(|r| f(r))
+    })
+}
+
+/// 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(|sc| f(&sc)))
+    })
+    .flatten()
+}
+
+pub(crate) struct Runtime {
+    pub(crate) scope_contexts: RefCell<Vec<Option<ScopeContext>>>,
+    pub(crate) scheduler: Rc<Scheduler>,
+
+    // We use this to track the current scope
+    pub(crate) scope_stack: RefCell<Vec<ScopeId>>,
+    pub(crate) rendering: Cell<bool>,
+}
+
+impl Runtime {
+    pub(crate) fn new(scheduler: Rc<Scheduler>) -> Rc<Self> {
+        Rc::new(Self {
+            scheduler,
+
+            scope_contexts: Default::default(),
+
+            scope_stack: Default::default(),
+
+            rendering: Cell::new(true),
+        })
+    }
+
+    /// Create a scope context. This slab is synchronized with the scope slab.
+    pub(crate) fn create_context_at(&self, id: ScopeId, context: ScopeContext) {
+        let mut contexts = self.scope_contexts.borrow_mut();
+        if contexts.len() <= id.0 {
+            contexts.resize_with(id.0 + 1, Default::default);
+        }
+        contexts[id.0] = Some(context);
+    }
+
+    pub(crate) fn remove_context(&self, id: ScopeId) {
+        self.scope_contexts.borrow_mut()[id.0] = None;
+    }
+
+    /// Get the current scope id
+    pub fn current_scope_id(&self) -> Option<ScopeId> {
+        self.scope_stack.borrow().last().copied()
+    }
+
+    /// Get the context for any scope given its ID
+    ///
+    /// This is useful for inserting or removing contexts from a scope, or rendering out its root node
+    pub fn get_context(&self, id: ScopeId) -> Option<Ref<'_, ScopeContext>> {
+        Ref::filter_map(self.scope_contexts.borrow(), |contexts| {
+            contexts.get(id.0).and_then(|f| f.as_ref())
+        })
+        .ok()
+    }
+}
+
+pub(crate) struct RuntimeGuard(Rc<Runtime>);
+
+impl RuntimeGuard {
+    pub(crate) fn new(runtime: Rc<Runtime>) -> Self {
+        push_runtime(runtime.clone());
+        Self(runtime)
+    }
+}
+
+impl Drop for RuntimeGuard {
+    fn drop(&mut self) {
+        pop_runtime();
+    }
+}

+ 13 - 4
packages/core/src/scheduler/wait.rs

@@ -1,4 +1,4 @@
-use crate::{TaskId, VirtualDom};
+use crate::{runtime::RuntimeGuard, TaskId, VirtualDom};
 use std::task::Context;
 use std::task::Context;
 
 
 impl VirtualDom {
 impl VirtualDom {
@@ -7,7 +7,8 @@ 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 _runtime = RuntimeGuard::new(self.runtime.clone());
+        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,
@@ -17,14 +18,22 @@ impl VirtualDom {
 
 
         let mut cx = Context::from_waker(&task.waker);
         let mut cx = Context::from_waker(&task.waker);
 
 
+        // update the scope stack
+        self.runtime.scope_stack.borrow_mut().push(task.scope);
+        self.runtime.rendering.set(false);
+
         // 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];
-            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
             // Remove it from the scheduler
             tasks.try_remove(id.0);
             tasks.try_remove(id.0);
         }
         }
+
+        // Remove the scope from the stack
+        self.runtime.scope_stack.borrow_mut().pop();
+        self.runtime.rendering.set(true);
     }
     }
 }
 }

+ 33 - 26
packages/core/src/scope_arena.rs

@@ -3,6 +3,7 @@ use crate::{
     bump_frame::BumpFrame,
     bump_frame::BumpFrame,
     innerlude::DirtyScope,
     innerlude::DirtyScope,
     nodes::RenderReturn,
     nodes::RenderReturn,
+    scope_context::ScopeContext,
     scopes::{ScopeId, ScopeState},
     scopes::{ScopeId, ScopeState},
     virtual_dom::VirtualDom,
     virtual_dom::VirtualDom,
 };
 };
@@ -13,48 +14,49 @@ 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 {
-            parent,
-            id,
-            height,
-            name,
+        let scope = entry.insert(Box::new(ScopeState {
+            runtime: self.runtime.clone(),
+            context_id: id,
+
             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())
+        let context =
+            ScopeContext::new(name, id, parent_id, height, self.runtime.scheduler.clone());
+        self.runtime.create_context_at(id, context);
+
+        scope
     }
     }
 
 
     pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
     pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
+        self.runtime.scope_stack.borrow_mut().push(scope_id);
         // Cycle to the next frame and then reset it
         // Cycle to the next frame and then reset it
         // This breaks any latent references, invalidating every pointer referencing into it.
         // This breaks any latent references, invalidating every pointer referencing into it.
         // Remove all the outdated listeners
         // Remove all the outdated listeners
         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,21 +79,26 @@ 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,
-            id: scope.id,
+            height: context.height,
+            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
-        unsafe { allocated.extend_lifetime_ref() }
+        let result = unsafe { allocated.extend_lifetime_ref() };
+
+        self.runtime.scope_stack.borrow_mut().pop();
+
+        result
     }
     }
 }
 }

+ 335 - 0
packages/core/src/scope_context.rs

@@ -0,0 +1,335 @@
+use crate::{
+    innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
+    runtime::{with_current_scope, with_runtime},
+    Element, ScopeId, TaskId,
+};
+use rustc_hash::FxHashSet;
+use std::{
+    any::Any,
+    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(crate) 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<Box<dyn Any>>>,
+
+    pub(crate) tasks: Rc<Scheduler>,
+    pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
+}
+
+impl ScopeContext {
+    pub(crate) fn new(
+        name: &'static str,
+        id: ScopeId,
+        parent_id: Option<ScopeId>,
+        height: u32,
+        tasks: Rc<Scheduler>,
+    ) -> Self {
+        Self {
+            name,
+            id,
+            parent_id,
+            height,
+            suspended: Cell::new(false),
+            shared_contexts: RefCell::new(vec![]),
+            tasks,
+            spawned_tasks: RefCell::new(FxHashSet::default()),
+        }
+    }
+
+    pub fn parent_id(&self) -> Option<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_map(|any| any.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| {
+            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_map(|any| any.downcast_ref::<T>())
+                {
+                    return Some(shared.clone());
+                }
+                search_parent = parent.parent_id;
+            }
+            None
+        })
+        .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.downcast_mut::<T>() {
+                std::mem::swap(ctx, &mut value.clone());
+                return value;
+            }
+        }
+
+        // Else, just push it
+        contexts.push(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| {
+            runtime
+                .get_context(ScopeId(0))
+                .unwrap()
+                .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 [`crate::scopes::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()
+}
+
+#[doc(hidden)]
+/// Check if the virtual dom is currently inside of the body of a component
+pub fn vdom_is_rendering() -> bool {
+    with_runtime(|rt| rt.rendering.get()).unwrap_or_default()
+}
+
+/// Consume context from the current scope
+pub fn consume_context<T: 'static + Clone>() -> Option<T> {
+    with_current_scope(|cx| cx.consume_context::<T>()).flatten()
+}
+
+/// Consume context from the current scope
+pub fn consume_context_from_scope<T: 'static + Clone>(scope_id: ScopeId) -> Option<T> {
+    with_runtime(|rt| {
+        rt.get_context(scope_id)
+            .and_then(|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 context to the the given scope
+pub fn provide_context_to_scope<T: 'static + Clone>(scope_id: ScopeId, value: T) -> Option<T> {
+    with_runtime(|rt| rt.get_context(scope_id).map(|cx| cx.provide_context(value))).flatten()
+}
+
+/// 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()
+}
+
+/// Pushes the future onto the poll queue to be polled after the component renders.
+pub fn push_future(fut: impl Future<Output = ()> + 'static) -> Option<TaskId> {
+    with_current_scope(|cx| cx.push_future(fut))
+}
+
+/// Spawns the future but does not return the [`TaskId`]
+pub fn spawn(fut: impl Future<Output = ()> + 'static) {
+    with_current_scope(|cx| cx.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(fut: impl Future<Output = ()> + 'static) -> Option<TaskId> {
+    with_current_scope(|cx| cx.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(id: TaskId) {
+    with_current_scope(|cx| cx.remove_future(id));
+}

+ 40 - 100
packages/core/src/scopes.rs

@@ -2,17 +2,18 @@ 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},
-    cell::{Cell, RefCell, UnsafeCell},
+    any::Any,
+    cell::{Cell, Ref, RefCell, UnsafeCell},
     fmt::{Arguments, Debug},
     fmt::{Arguments, Debug},
     future::Future,
     future::Future,
     rc::Rc,
     rc::Rc,
@@ -66,33 +67,34 @@ 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>>>,
 
 
     pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
     pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
 }
 }
 
 
+impl Drop for ScopeState {
+    fn drop(&mut self) {
+        self.runtime.remove_context(self.context_id);
+    }
+}
+
 impl<'src> ScopeState {
 impl<'src> ScopeState {
+    pub(crate) fn context(&self) -> Ref<'_, 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 +113,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 +176,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`].
@@ -195,7 +197,7 @@ impl<'src> ScopeState {
     /// ```
     /// ```
     pub fn parent(&self) -> Option<ScopeId> {
     pub fn parent(&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 +214,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());
-        Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
+        self.context().schedule_update()
     }
     }
 
 
     /// Schedule an update for any component given its [`ScopeId`].
     /// Schedule an update for any component given its [`ScopeId`].
@@ -229,61 +230,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();
-        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.
     /// 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
-            .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
     /// 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
-            .borrow()
-            .iter()
-            .find(|(k, _)| *k == TypeId::of::<T>())
-            .map(|(_, v)| v)?
-            .downcast_ref::<T>()
-            .cloned()
+        self.context().has_context()
     }
     }
 
 
     /// Try to retrieve a shared state with type `T` from any parent scope.
     /// 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() {
-            return Some(this_ctx);
-        }
-
-        let mut search_parent = self.parent;
-        while let Some(parent_ptr) = search_parent {
-            // safety: all parent pointers are valid thanks to the bump arena
-            let parent = unsafe { &*parent_ptr };
-            if let Some(shared) = parent
-                .shared_contexts
-                .borrow()
-                .iter()
-                .find(|(k, _)| *k == TypeId::of::<T>())
-            {
-                return shared.1.downcast_ref::<T>().cloned();
-            }
-            search_parent = parent.parent;
-        }
-        None
+        self.context().consume_context()
     }
     }
 
 
     /// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree.
     /// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree.
@@ -308,21 +279,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();
-
-        // If the context exists, swap it out for the new value
-        for ctx in contexts.iter_mut() {
-            // Swap the ptr directly
-            if let Some(ctx) = ctx.1.downcast_mut::<T>() {
-                std::mem::swap(ctx, &mut value.clone());
-                return value;
-            }
-        }
-
-        // Else, just push it
-        contexts.push((TypeId::of::<T>(), Box::new(value.clone())));
-
-        value
+        self.context().provide_context(value)
     }
     }
 
 
     /// Provide a context to the root and then consume it
     /// Provide a context to the root and then consume it
@@ -333,52 +290,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;
-
-        // 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.
     /// 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.spawned_tasks.borrow_mut().insert(id);
-        id
+        self.context().push_future(fut)
     }
     }
 
 
     /// 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
-        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.
     /// 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.
@@ -511,7 +447,10 @@ impl<'src> ScopeState {
         let handler: &mut dyn FnMut(T) = self.bump().alloc(f);
         let handler: &mut dyn FnMut(T) = self.bump().alloc(f);
         let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) };
         let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) };
         let callback = RefCell::new(Some(caller));
         let callback = RefCell::new(Some(caller));
-        EventHandler { callback }
+        EventHandler {
+            callback,
+            origin: self.context().id,
+        }
     }
     }
 
 
     /// Create a new [`AttributeValue`] with the listener variant from a callback
     /// Create a new [`AttributeValue`] with the listener variant from a callback
@@ -565,7 +504,8 @@ 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);
+        let cx = self.context();
+        cx.suspend();
         None
         None
     }
     }
 
 

+ 38 - 20
packages/core/src/virtual_dom.rs

@@ -9,6 +9,7 @@ use crate::{
     mutations::Mutation,
     mutations::Mutation,
     nodes::RenderReturn,
     nodes::RenderReturn,
     nodes::{Template, TemplateId},
     nodes::{Template, TemplateId},
+    runtime::{Runtime, RuntimeGuard},
     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 {
-    // Maps a template path to a map of byteindexes to templates
-    pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
     pub(crate) scopes: Slab<Box<ScopeState>>,
     pub(crate) scopes: Slab<Box<ScopeState>>,
+
     pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
     pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
-    pub(crate) scheduler: Rc<Scheduler>,
+
+    // Maps a template path to a map of byteindexes to templates
+    pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
 
 
     // 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) scope_stack: Vec<ScopeId>,
+    pub(crate) mutations: Mutations<'static>,
+
+    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),
-            templates: Default::default(),
+            runtime: Runtime::new(scheduler),
             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).map(|s| &**s)
     }
     }
 
 
     /// 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 });
         }
         }
     }
     }
@@ -325,6 +326,8 @@ impl VirtualDom {
         element: ElementId,
         element: ElementId,
         bubbles: bool,
         bubbles: bool,
     ) {
     ) {
+        let _runtime = RuntimeGuard::new(self.runtime.clone());
+
         /*
         /*
         ------------------------
         ------------------------
         The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
         The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
@@ -387,9 +390,14 @@ impl VirtualDom {
                     // We check the bubble state between each call to see if the event has been stopped from bubbling
                     // We check the bubble state between each call to see if the event has been stopped from bubbling
                     for listener in listeners.drain(..).rev() {
                     for listener in listeners.drain(..).rev() {
                         if let AttributeValue::Listener(listener) = listener {
                         if let AttributeValue::Listener(listener) = listener {
+                            let origin = el_ref.scope;
+                            self.runtime.scope_stack.borrow_mut().push(origin);
+                            self.runtime.rendering.set(false);
                             if let Some(cb) = listener.borrow_mut().as_deref_mut() {
                             if let Some(cb) = listener.borrow_mut().as_deref_mut() {
                                 cb(uievent.clone());
                                 cb(uievent.clone());
                             }
                             }
+                            self.runtime.scope_stack.borrow_mut().pop();
+                            self.runtime.rendering.set(true);
 
 
                             if !uievent.propagates.get() {
                             if !uievent.propagates.get() {
                                 return;
                                 return;
@@ -418,9 +426,14 @@ impl VirtualDom {
                         // Only call the listener if this is the exact target element.
                         // Only call the listener if this is the exact target element.
                         if attr.name.trim_start_matches("on") == name && target_path == this_path {
                         if attr.name.trim_start_matches("on") == name && target_path == this_path {
                             if let AttributeValue::Listener(listener) = &attr.value {
                             if let AttributeValue::Listener(listener) = &attr.value {
+                                let origin = el_ref.scope;
+                                self.runtime.scope_stack.borrow_mut().push(origin);
+                                self.runtime.rendering.set(false);
                                 if let Some(cb) = listener.borrow_mut().as_deref_mut() {
                                 if let Some(cb) = listener.borrow_mut().as_deref_mut() {
                                     cb(uievent.clone());
                                     cb(uievent.clone());
                                 }
                                 }
+                                self.runtime.scope_stack.borrow_mut().pop();
+                                self.runtime.rendering.set(true);
 
 
                                 break;
                                 break;
                             }
                             }
@@ -501,10 +514,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,
                     });
                     });
                 }
                 }
             }
             }
@@ -532,6 +546,7 @@ impl VirtualDom {
     /// apply_edits(edits);
     /// apply_edits(edits);
     /// ```
     /// ```
     pub fn rebuild(&mut self) -> Mutations {
     pub fn rebuild(&mut self) -> Mutations {
+        let _runtime = RuntimeGuard::new(self.runtime.clone());
         match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
         match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
             // Rebuilding implies we append the created elements to the root
             // Rebuilding implies we append the created elements to the root
             RenderReturn::Ready(node) => {
             RenderReturn::Ready(node) => {
@@ -610,9 +625,12 @@ impl VirtualDom {
                     continue;
                     continue;
                 }
                 }
 
 
-                // Run the scope and get the mutations
-                self.run_scope(dirty.id);
-                self.diff_scope(dirty.id);
+                {
+                    let _runtime = RuntimeGuard::new(self.runtime.clone());
+                    // Run the scope and get the mutations
+                    self.run_scope(dirty.id);
+                    self.diff_scope(dirty.id);
+                }
             }
             }
 
 
             // If there's more work, then just continue, plenty of work to do
             // If there's more work, then just continue, plenty of work to do

+ 7 - 2
packages/desktop/src/protocol.rs

@@ -158,8 +158,13 @@ fn get_mime_from_path(trimmed: &Path) -> Result<&'static str> {
     }
     }
 
 
     let res = match infer::get_from_path(trimmed)?.map(|f| f.mime_type()) {
     let res = match infer::get_from_path(trimmed)?.map(|f| f.mime_type()) {
-        Some(t) if t == "text/plain" => get_mime_by_ext(trimmed),
-        Some(f) => f,
+        Some(f) => {
+            if f == "text/plain" {
+                get_mime_by_ext(trimmed)
+            } else {
+                f
+            }
+        }
         None => get_mime_by_ext(trimmed),
         None => get_mime_by_ext(trimmed),
     };
     };
 
 

+ 7 - 1
packages/dioxus-tui/examples/hover.rs

@@ -1,6 +1,7 @@
 use dioxus::{events::MouseData, prelude::*};
 use dioxus::{events::MouseData, prelude::*};
 use dioxus_core::Event;
 use dioxus_core::Event;
 use std::convert::TryInto;
 use std::convert::TryInto;
+use std::fmt::Write;
 use std::rc::Rc;
 use std::rc::Rc;
 
 
 fn main() {
 fn main() {
@@ -9,7 +10,12 @@ fn main() {
 
 
 fn app(cx: Scope) -> Element {
 fn app(cx: Scope) -> Element {
     fn to_str(c: &[i32; 3]) -> String {
     fn to_str(c: &[i32; 3]) -> String {
-        "#".to_string() + &c.iter().map(|c| format!("{c:02X?}")).collect::<String>()
+        let mut result = String::new();
+        result += "#";
+        for c in c.iter() {
+            write!(result, "{c:02X?}").unwrap();
+        }
+        result
     }
     }
 
 
     fn get_brightness(m: &Rc<MouseData>) -> i32 {
     fn get_brightness(m: &Rc<MouseData>) -> i32 {

+ 17 - 0
packages/generational-box/Cargo.toml

@@ -0,0 +1,17 @@
+[package]
+name = "generational-box"
+authors = ["Evan Almloff"]
+version = "0.0.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+bumpalo = { version = "3.6" }
+
+[dev-dependencies]
+rand = "0.8.5"
+
+[features]
+default = ["check_generation"]
+check_generation = []

+ 34 - 0
packages/generational-box/README.md

@@ -0,0 +1,34 @@
+# Generational Box
+
+Generational Box is a runtime for Rust that allows any static type to implement `Copy`. It can be combined with a global runtime to create an ergonomic state solution like `dioxus-signals`. This crate contains no `unsafe` code.
+
+Three main types manage state in Generational Box:
+
+- Store: Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread.
+- Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped.
+- GenerationalBox: The core Copy state type. The generational box will be dropped when the owner is dropped.
+
+Example:
+
+```rust
+// Create a store for this thread
+let store = Store::default();
+
+{
+    // Create an owner for some state for a scope
+    let owner = store.owner();
+
+    // Create some non-copy data, move it into a owner, and work with copy data
+    let data: String = "hello world".to_string();
+    let key = owner.insert(data);
+    
+    // The generational box can be read from and written to like a RefCell
+    let value = key.read();
+    assert_eq!(*value, "hello world");
+}
+// Reading value at this point will cause a panic
+```
+
+## How it works
+
+Internally 

+ 359 - 0
packages/generational-box/src/lib.rs

@@ -0,0 +1,359 @@
+#![doc = include_str!("../README.md")]
+#![warn(missing_docs)]
+
+use std::{
+    cell::{Cell, Ref, RefCell, RefMut},
+    fmt::Debug,
+    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<GenerationalBox<String>>,
+        invalid_keys: &mut Vec<GenerationalBox<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(any(debug_assertions, feature = "check_generation"))]
+            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());
+    }
+}
+
+/// The core Copy state type. The generational box will be dropped when the [Owner] is dropped.
+pub struct GenerationalBox<T> {
+    raw: MemoryLocation,
+    #[cfg(any(debug_assertions, feature = "check_generation"))]
+    generation: u32,
+    _marker: PhantomData<T>,
+}
+
+impl<T: 'static> Debug for GenerationalBox<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        #[cfg(any(debug_assertions, feature = "check_generation"))]
+        f.write_fmt(format_args!(
+            "{:?}@{:?}",
+            self.raw.data.as_ptr(),
+            self.generation
+        ))?;
+        #[cfg(not(any(debug_assertions, feature = "check_generation")))]
+        f.write_fmt(format_args!("{:?}", self.raw.data.as_ptr()))?;
+        Ok(())
+    }
+}
+
+impl<T: 'static> GenerationalBox<T> {
+    #[inline(always)]
+    fn validate(&self) -> bool {
+        #[cfg(any(debug_assertions, feature = "check_generation"))]
+        {
+            self.raw.generation.get() == self.generation
+        }
+        #[cfg(not(any(debug_assertions, feature = "check_generation")))]
+        {
+            true
+        }
+    }
+
+    /// Try to read the value. Returns None if the value is no longer valid.
+    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()
+    }
+
+    /// Read the value. Panics if the value is no longer valid.
+    pub fn read(&self) -> Ref<'_, T> {
+        self.try_read().unwrap()
+    }
+
+    /// Try to write the value. Returns None if the value is no longer valid.
+    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()
+    }
+
+    /// Write the value. Panics if the value is no longer valid.
+    pub fn write(&self) -> RefMut<'_, T> {
+        self.try_write().unwrap()
+    }
+
+    /// Set the value. Panics if the value is no longer valid.
+    pub fn set(&self, value: T) {
+        self.validate().then(|| {
+            *self.raw.data.borrow_mut() = Some(Box::new(value));
+        });
+    }
+
+    /// Returns true if the pointer is equal to the other pointer.
+    pub fn ptr_eq(&self, other: &Self) -> bool {
+        #[cfg(any(debug_assertions, feature = "check_generation"))]
+        {
+            self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation
+        }
+        #[cfg(not(any(debug_assertions, feature = "check_generation")))]
+        {
+            self.raw.data.as_ptr() == other.raw.data.as_ptr()
+        }
+    }
+}
+
+impl<T> Copy for GenerationalBox<T> {}
+
+impl<T> Clone for GenerationalBox<T> {
+    fn clone(&self) -> Self {
+        *self
+    }
+}
+
+#[derive(Clone, Copy)]
+struct MemoryLocation {
+    data: &'static RefCell<Option<Box<dyn std::any::Any>>>,
+    #[cfg(any(debug_assertions, feature = "check_generation"))]
+    generation: &'static Cell<u32>,
+}
+
+impl MemoryLocation {
+    #[allow(unused)]
+    fn drop(&self) {
+        let old = self.data.borrow_mut().take();
+        #[cfg(any(debug_assertions, feature = "check_generation"))]
+        if old.is_some() {
+            drop(old);
+            let new_generation = self.generation.get() + 1;
+            self.generation.set(new_generation);
+        }
+    }
+
+    fn replace<T: 'static>(&mut self, value: T) -> GenerationalBox<T> {
+        let mut inner_mut = self.data.borrow_mut();
+
+        let raw = Box::new(value);
+        let old = inner_mut.replace(raw);
+        assert!(old.is_none());
+        GenerationalBox {
+            raw: *self,
+            #[cfg(any(debug_assertions, feature = "check_generation"))]
+            generation: self.generation.get(),
+            _marker: PhantomData,
+        }
+    }
+}
+
+/// Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread.
+#[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(any(debug_assertions, feature = "check_generation"))]
+                generation: self.bump.alloc(Cell::new(0)),
+            }
+        }
+    }
+
+    /// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates.
+    pub fn owner(&self) -> Owner {
+        Owner {
+            store: self.clone(),
+            owned: Default::default(),
+        }
+    }
+}
+
+/// Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped.
+pub struct Owner {
+    store: Store,
+    owned: Rc<RefCell<Vec<MemoryLocation>>>,
+}
+
+impl Owner {
+    /// Insert a value into the store. The value will be dropped when the owner is dropped.
+    pub fn insert<T: 'static>(&self, value: T) -> GenerationalBox<T> {
+        let mut location = self.store.claim();
+        let key = location.replace(value);
+        self.owned.borrow_mut().push(location);
+        key
+    }
+
+    /// Creates an invalid handle. This is useful for creating a handle that will be filled in later. If you use this before the value is filled in, you will get may get a panic or an out of date value.
+    pub fn invalid<T: 'static>(&self) -> GenerationalBox<T> {
+        let location = self.store.claim();
+        GenerationalBox {
+            raw: location,
+            #[cfg(any(debug_assertions, feature = "check_generation"))]
+            generation: location.generation.get(),
+            _marker: PhantomData,
+        }
+    }
+}
+
+impl Drop for Owner {
+    fn drop(&mut self) {
+        for location in self.owned.borrow().iter() {
+            self.store.recycle(*location)
+        }
+    }
+}

+ 2 - 0
packages/hooks/src/computed.rs

@@ -1,3 +1,5 @@
+//! Tracked and computed state in Dioxus
+
 use dioxus_core::{ScopeId, ScopeState};
 use dioxus_core::{ScopeId, ScopeState};
 use slab::Slab;
 use slab::Slab;
 use std::{
 use std::{

+ 1 - 2
packages/hooks/src/lib.rs

@@ -52,8 +52,7 @@ macro_rules! to_owned {
     };
     };
 }
 }
 
 
-mod computed;
-pub use computed::*;
+pub mod computed;
 
 
 mod use_on_unmount;
 mod use_on_unmount;
 pub use use_on_unmount::*;
 pub use use_on_unmount::*;

+ 1 - 1
packages/html/src/eval.rs

@@ -42,7 +42,7 @@ pub fn use_eval(cx: &ScopeState) -> &EvalCreator {
         Rc::new(move |script: &str| {
         Rc::new(move |script: &str| {
             eval_provider
             eval_provider
                 .new_evaluator(script.to_string())
                 .new_evaluator(script.to_string())
-                .map(|evaluator| UseEval::new(evaluator))
+                .map(UseEval::new)
         }) as Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>
         }) as Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>
     })
     })
 }
 }

+ 0 - 1
packages/native-core/src/real_dom.rs

@@ -446,7 +446,6 @@ impl<V: FromAnyValue + Send + Sync> RealDom<V> {
             drop(tree);
             drop(tree);
             children.reverse();
             children.reverse();
             if let Some(node) = self.get_mut(id) {
             if let Some(node) = self.get_mut(id) {
-                let node = node;
                 f(node);
                 f(node);
                 stack.extend(children.iter());
                 stack.extend(children.iter());
             }
             }

+ 2 - 8
packages/rsx/src/lib.rs

@@ -288,10 +288,7 @@ impl DynamicMapping {
         let idx = self.last_attribute_idx;
         let idx = self.last_attribute_idx;
         self.last_attribute_idx += 1;
         self.last_attribute_idx += 1;
 
 
-        self.attribute_to_idx
-            .entry(attr)
-            .or_insert_with(Vec::new)
-            .push(idx);
+        self.attribute_to_idx.entry(attr).or_default().push(idx);
 
 
         idx
         idx
     }
     }
@@ -300,10 +297,7 @@ impl DynamicMapping {
         let idx = self.last_element_idx;
         let idx = self.last_element_idx;
         self.last_element_idx += 1;
         self.last_element_idx += 1;
 
 
-        self.node_to_idx
-            .entry(node)
-            .or_insert_with(Vec::new)
-            .push(idx);
+        self.node_to_idx.entry(node).or_default().push(idx);
 
 
         idx
         idx
     }
     }

+ 13 - 1
packages/signals/Cargo.toml

@@ -8,4 +8,16 @@ edition = "2018"
 
 
 [dependencies]
 [dependencies]
 dioxus-core = { workspace = true }
 dioxus-core = { workspace = true }
-slab = { workspace = true }
+generational-box = { workspace = true }
+log.workspace = true
+simple_logger = "4.2.0"
+serde = { version = "1", features = ["derive"], optional = true }
+
+[dev-dependencies]
+dioxus = { workspace = true }
+dioxus-desktop = { workspace = true }
+tokio = { version = "1", features = ["full"] }
+
+[features]
+default = []
+serialize = ["serde"]

+ 122 - 0
packages/signals/README.md

@@ -0,0 +1,122 @@
+# Dioxus Signals
+
+Dioxus Signals is an ergonomic Copy runtime for data with local subscriptions.
+
+## Copy Data
+
+All signals implement Copy, even if the inner value does not implement copy. This makes it easy to move any data into futures or children.
+
+```rust
+use dioxus::prelude::*;
+use dioxus_signals::*;
+
+fn app(cx: Scope) -> Element {
+    let signal = use_signal(cx, || "hello world".to_string());
+
+    spawn(async move {
+        // signal is Copy even though String is not copy
+        print!("{signal}");
+    });
+
+    render! {
+        "{signal}"
+    }
+}
+```
+
+## Local Subscriptions
+
+Signals will only subscribe to components when you read from the signal in that component. It will never subscribe to a component when reading data in a future or event handler.
+
+```rust
+use dioxus::prelude::*;
+use dioxus_signals::*;
+
+fn app(cx: Scope) -> Element {
+    // Because signal is never read in this component, this component will not rerun when the signal changes
+    let signal = use_signal(cx, || 0);
+
+    render! {
+        button {
+            onclick: move |_| {
+                *signal.write() += 1;
+            },
+            "Increase"
+        }
+        for id in 0..10 {
+            Child {
+                signal: signal,
+            }
+        }
+    }
+}
+
+#[derive(Props, Clone, PartialEq)]
+struct ChildProps {
+    signal: Signal<usize>,
+}
+
+fn Child(cx: Scope<ChildProps>) -> Element {
+    // This component does read from the signal, so when the signal changes it will rerun
+    render! {
+        "{cx.props.signal}"
+    }
+}
+```
+
+Because subscriptions happen when you read from (not create) the data, you can provide signals through the normal context API:
+
+```rust
+use dioxus::prelude::*;
+use dioxus_signals::*;
+
+fn app(cx: Scope) -> Element {
+    // Because signal is never read in this component, this component will not rerun when the signal changes
+    use_context_provider(cx, || Signal::new(0));
+
+    render! {
+        Child {}
+    }
+}
+
+fn Child(cx: Scope) -> Element {
+    let signal: Signal<i32> = *use_context(cx).unwrap();
+    // This component does read from the signal, so when the signal changes it will rerun
+    render! {
+        "{signal}"
+    }
+}
+```
+
+## Computed Data
+
+In addition to local subscriptions in components, `dioxus-signals` provides a way to derive data with local subscriptions.
+
+The use_selector hook will only rerun when any signals inside of the hook change:
+
+```rust
+use dioxus::prelude::*;
+use dioxus_signals::*;
+
+fn app(cx: Scope) -> Element {
+    let signal = use_signal(cx, || 0);
+    let doubled = use_selector(cx, || signal * 2);
+
+    render! {
+        button {
+            onclick: move |_| *signal.write() += 1,
+            "Increase"
+        }
+        Child {
+            signal: signal
+        }
+    }
+}
+
+#[inline_props]
+fn Child(cx: Scope, signal: ReadOnlySignal<usize>) -> Element {
+    render! {
+        "{signal}"
+    }
+}
+```

+ 25 - 0
packages/signals/examples/context.rs

@@ -0,0 +1,25 @@
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
+use dioxus_signals::Signal;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    // Because signal is never read in this component, this component will not rerun when the signal changes
+    use_context_provider(cx, || Signal::new(0));
+
+    render! {
+        Child {}
+    }
+}
+
+fn Child(cx: Scope) -> Element {
+    let signal: Signal<i32> = *use_context(cx).unwrap();
+    // This component does read from the signal, so when the signal changes it will rerun
+    render! {
+        "{signal}"
+    }
+}

+ 38 - 0
packages/signals/examples/dependancies.rs

@@ -0,0 +1,38 @@
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
+use dioxus_signals::*;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let signal = use_signal(cx, || 0);
+
+    use_future!(cx, || async move {
+        loop {
+            tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+            *signal.write() += 1;
+        }
+    });
+
+    let local_state = use_state(cx, || 0);
+    let computed =
+        use_selector_with_dependencies(cx, (local_state.get(),), move |(local_state,)| {
+            local_state * 2 + signal.value()
+        });
+    println!("Running app");
+
+    render! {
+        button {
+            onclick: move |_| {
+                local_state.set(local_state.get() + 1);
+            },
+            "Add one"
+        }
+        div {
+            "{computed}"
+        }
+    }
+}

+ 30 - 0
packages/signals/examples/selector.rs

@@ -0,0 +1,30 @@
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
+use dioxus_signals::*;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let signal = use_signal(cx, || 0);
+    let doubled = use_selector(cx, move || signal * 2);
+
+    render! {
+        button {
+            onclick: move |_| *signal.write() += 1,
+            "Increase"
+        }
+        Child {
+            signal: doubled
+        }
+    }
+}
+
+#[inline_props]
+fn Child(cx: Scope, signal: ReadOnlySignal<usize>) -> Element {
+    render! {
+        "{signal}"
+    }
+}

+ 150 - 0
packages/signals/examples/split_subscriptions.rs

@@ -0,0 +1,150 @@
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
+use dioxus_signals::Signal;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+#[derive(Clone, Copy, Default)]
+struct ApplicationData {
+    first_data: Signal<i32>,
+    second_data: Signal<i32>,
+    many_signals: Signal<Vec<Signal<i32>>>,
+}
+
+fn use_app_data(cx: Scope) -> ApplicationData {
+    *use_context(cx).unwrap()
+}
+
+fn app(cx: Scope) -> Element {
+    use_context_provider(cx, ApplicationData::default);
+
+    render! {
+        div {
+            ReadsFirst {}
+        }
+        div {
+            ReadsSecond {}
+        }
+        div {
+            ReadsManySignals {}
+        }
+    }
+}
+
+fn ReadsFirst(cx: Scope) -> Element {
+    println!("running first");
+    let data = use_app_data(cx);
+
+    render! {
+        button {
+            onclick: move |_| {
+                *data.first_data.write() += 1;
+            },
+            "Increase"
+        }
+        button {
+            onclick: move |_| {
+                *data.first_data.write() -= 1;
+            },
+            "Decrease"
+        }
+        button {
+            onclick: move |_| {
+                *data.first_data.write() = 0;
+            },
+            "Reset"
+        }
+        "{data.first_data}"
+    }
+}
+
+fn ReadsSecond(cx: Scope) -> Element {
+    println!("running second");
+    let data = use_app_data(cx);
+
+    render! {
+        button {
+            onclick: move |_| {
+                *data.second_data.write() += 1;
+            },
+            "Increase"
+        }
+        button {
+            onclick: move |_| {
+                *data.second_data.write() -= 1;
+            },
+            "Decrease"
+        }
+        button {
+            onclick: move |_| {
+                *data.second_data.write() = 0;
+            },
+            "Reset"
+        }
+        "{data.second_data}"
+    }
+}
+
+fn ReadsManySignals(cx: Scope) -> Element {
+    println!("running many signals");
+    let data = use_app_data(cx);
+
+    render! {
+        button {
+            onclick: move |_| {
+                data.many_signals.write().push(Signal::new(0));
+            },
+            "Create"
+        }
+        button {
+            onclick: move |_| {
+                data.many_signals.write().pop();
+            },
+            "Destroy"
+        }
+        button {
+            onclick: move |_| {
+                if let Some(first) = data.many_signals.read().get(0) {
+                    *first.write() += 1;
+                }
+            },
+            "Increase First Item"
+        }
+        for signal in data.many_signals {
+            Child {
+                count: signal,
+            }
+        }
+    }
+}
+
+#[derive(Props, PartialEq)]
+struct ChildProps {
+    count: Signal<i32>,
+}
+
+fn Child(cx: Scope<ChildProps>) -> Element {
+    println!("running child");
+    let count = cx.props.count;
+
+    render! {
+        div {
+            "Child: {count}"
+            button {
+                onclick: move |_| {
+                    *count.write() += 1;
+                },
+                "Increase"
+            }
+            button {
+                onclick: move |_| {
+                    *count.write() -= 1;
+                },
+                "Decrease"
+            }
+        }
+    }
+}

+ 67 - 0
packages/signals/src/dependency.rs

@@ -0,0 +1,67 @@
+/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.
+pub trait Dependency: Sized + Clone {
+    /// The output of the dependency
+    type Out: Clone + PartialEq;
+    /// Returns the output of the dependency.
+    fn out(&self) -> Self::Out;
+    /// Returns true if the dependency has changed.
+    fn changed(&self, other: &Self::Out) -> bool {
+        self.out() != *other
+    }
+}
+
+impl Dependency for () {
+    type Out = ();
+    fn out(&self) -> Self::Out {}
+}
+
+/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.
+pub trait Dep: 'static + PartialEq + Clone {}
+impl<T> Dep for T where T: 'static + PartialEq + Clone {}
+
+impl<A: Dep> Dependency for &A {
+    type Out = A;
+    fn out(&self) -> Self::Out {
+        (*self).clone()
+    }
+}
+
+macro_rules! impl_dep {
+    (
+        $($el:ident=$name:ident $other:ident,)*
+    ) => {
+        impl< $($el),* > Dependency for ($(&$el,)*)
+        where
+            $(
+                $el: Dep
+            ),*
+        {
+            type Out = ($($el,)*);
+
+            fn out(&self) -> Self::Out {
+                let ($($name,)*) = self;
+                ($((*$name).clone(),)*)
+            }
+
+            fn changed(&self, other: &Self::Out) -> bool {
+                let ($($name,)*) = self;
+                let ($($other,)*) = other;
+                $(
+                    if *$name != $other {
+                        return true;
+                    }
+                )*
+                false
+            }
+        }
+    };
+}
+
+impl_dep!(A = a1 a2,);
+impl_dep!(A = a1 a2, B = b1 b2,);
+impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2,);
+impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2,);
+impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2,);
+impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2,);
+impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G = g1 g2,);
+impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G = g1 g2, H = h1 h2,);

+ 96 - 0
packages/signals/src/effect.rs

@@ -0,0 +1,96 @@
+use core::{self, fmt::Debug};
+use std::cell::RefCell;
+use std::fmt::{self, Formatter};
+use std::rc::Rc;
+//
+use dioxus_core::prelude::*;
+
+use crate::use_signal;
+use crate::{dependency::Dependency, CopyValue};
+
+#[derive(Default, Clone)]
+pub(crate) struct EffectStack {
+    pub(crate) effects: Rc<RefCell<Vec<Effect>>>,
+}
+
+pub(crate) fn get_effect_stack() -> EffectStack {
+    match consume_context() {
+        Some(rt) => rt,
+        None => {
+            let store = EffectStack::default();
+            provide_root_context(store).expect("in a virtual dom")
+        }
+    }
+}
+
+/// Create a new effect. The effect will be run immediately and whenever any signal it reads changes.
+/// The signal will be owned by the current component and will be dropped when the component is dropped.
+pub fn use_effect(cx: &ScopeState, callback: impl FnMut() + 'static) {
+    cx.use_hook(|| Effect::new(callback));
+}
+
+/// Create a new effect. The effect will be run immediately and whenever any signal it reads changes.
+/// The signal will be owned by the current component and will be dropped when the component is dropped.
+pub fn use_effect_with_dependencies<D: Dependency>(
+    cx: &ScopeState,
+    dependencies: D,
+    mut callback: impl FnMut(D::Out) + 'static,
+) where
+    D::Out: 'static,
+{
+    let dependencies_signal = use_signal(cx, || dependencies.out());
+    cx.use_hook(|| {
+        Effect::new(move || {
+            let deref = &*dependencies_signal.read();
+            callback(deref.clone());
+        });
+    });
+    let changed = { dependencies.changed(&*dependencies_signal.read()) };
+    if changed {
+        dependencies_signal.set(dependencies.out());
+    }
+}
+
+/// Effects allow you to run code when a signal changes. Effects are run immediately and whenever any signal it reads changes.
+#[derive(Copy, Clone, PartialEq)]
+pub struct Effect {
+    pub(crate) callback: CopyValue<Box<dyn FnMut()>>,
+}
+
+impl Debug for Effect {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        f.write_fmt(format_args!("{:?}", self.callback.value))
+    }
+}
+
+impl Effect {
+    pub(crate) fn current() -> Option<Self> {
+        get_effect_stack().effects.borrow().last().copied()
+    }
+
+    /// Create a new effect. The effect will be run immediately and whenever any signal it reads changes.
+    ///
+    /// The signal will be owned by the current component and will be dropped when the component is dropped.
+    pub fn new(callback: impl FnMut() + 'static) -> Self {
+        let myself = Self {
+            callback: CopyValue::new(Box::new(callback)),
+        };
+
+        myself.try_run();
+
+        myself
+    }
+
+    /// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead.
+    pub fn try_run(&self) {
+        if let Some(mut callback) = self.callback.try_write() {
+            {
+                get_effect_stack().effects.borrow_mut().push(*self);
+            }
+            callback();
+            {
+                get_effect_stack().effects.borrow_mut().pop();
+            }
+        }
+    }
+}

+ 294 - 0
packages/signals/src/impls.rs

@@ -0,0 +1,294 @@
+use crate::rt::CopyValue;
+use crate::signal::{ReadOnlySignal, Signal, Write};
+
+use std::cell::{Ref, RefMut};
+
+use std::{
+    fmt::{Debug, Display},
+    ops::{Add, Div, Mul, Sub},
+};
+
+macro_rules! read_impls {
+    ($ty:ident) => {
+        impl<T: Default + 'static> Default for $ty<T> {
+            fn default() -> Self {
+                Self::new(Default::default())
+            }
+        }
+
+        impl<T> std::clone::Clone for $ty<T> {
+            fn clone(&self) -> Self {
+                *self
+            }
+        }
+
+        impl<T> Copy for $ty<T> {}
+
+        impl<T: Display + 'static> Display for $ty<T> {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                self.with(|v| Display::fmt(v, f))
+            }
+        }
+
+        impl<T: Debug + 'static> Debug for $ty<T> {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                self.with(|v| Debug::fmt(v, f))
+            }
+        }
+
+        impl<T: 'static> $ty<Vec<T>> {
+            /// Read a value from the inner vector.
+            pub fn get(&self, index: usize) -> Option<Ref<'_, T>> {
+                Ref::filter_map(self.read(), |v| v.get(index)).ok()
+            }
+        }
+
+        impl<T: 'static> $ty<Option<T>> {
+            /// Unwraps the inner value and clones it.
+            pub fn unwrap(&self) -> T
+            where
+                T: Clone,
+            {
+                self.with(|v| v.clone()).unwrap()
+            }
+
+            /// Attemps to read the inner value of the Option.
+            pub fn as_ref(&self) -> Option<Ref<'_, T>> {
+                Ref::filter_map(self.read(), |v| v.as_ref()).ok()
+            }
+        }
+    };
+}
+
+macro_rules! write_impls {
+    ($ty:ident) => {
+        impl<T: Add<Output = T> + Copy + 'static> std::ops::Add<T> for $ty<T> {
+            type Output = T;
+
+            fn add(self, rhs: T) -> Self::Output {
+                self.with(|v| *v + rhs)
+            }
+        }
+
+        impl<T: Add<Output = T> + Copy + 'static> std::ops::AddAssign<T> for $ty<T> {
+            fn add_assign(&mut self, rhs: T) {
+                self.with_mut(|v| *v = *v + rhs)
+            }
+        }
+
+        impl<T: Sub<Output = T> + Copy + 'static> std::ops::SubAssign<T> for $ty<T> {
+            fn sub_assign(&mut self, rhs: T) {
+                self.with_mut(|v| *v = *v - rhs)
+            }
+        }
+
+        impl<T: Sub<Output = T> + Copy + 'static> std::ops::Sub<T> for $ty<T> {
+            type Output = T;
+
+            fn sub(self, rhs: T) -> Self::Output {
+                self.with(|v| *v - rhs)
+            }
+        }
+
+        impl<T: Mul<Output = T> + Copy + 'static> std::ops::MulAssign<T> for $ty<T> {
+            fn mul_assign(&mut self, rhs: T) {
+                self.with_mut(|v| *v = *v * rhs)
+            }
+        }
+
+        impl<T: Mul<Output = T> + Copy + 'static> std::ops::Mul<T> for $ty<T> {
+            type Output = T;
+
+            fn mul(self, rhs: T) -> Self::Output {
+                self.with(|v| *v * rhs)
+            }
+        }
+
+        impl<T: Div<Output = T> + Copy + 'static> std::ops::DivAssign<T> for $ty<T> {
+            fn div_assign(&mut self, rhs: T) {
+                self.with_mut(|v| *v = *v / rhs)
+            }
+        }
+
+        impl<T: Div<Output = T> + Copy + 'static> std::ops::Div<T> for $ty<T> {
+            type Output = T;
+
+            fn div(self, rhs: T) -> Self::Output {
+                self.with(|v| *v / rhs)
+            }
+        }
+
+        impl<T: 'static> $ty<Vec<T>> {
+            /// Pushes a new value to the end of the vector.
+            pub fn push(&self, value: T) {
+                self.with_mut(|v| v.push(value))
+            }
+
+            /// Pops the last value from the vector.
+            pub fn pop(&self) -> Option<T> {
+                self.with_mut(|v| v.pop())
+            }
+
+            /// Inserts a new value at the given index.
+            pub fn insert(&self, index: usize, value: T) {
+                self.with_mut(|v| v.insert(index, value))
+            }
+
+            /// Removes the value at the given index.
+            pub fn remove(&self, index: usize) -> T {
+                self.with_mut(|v| v.remove(index))
+            }
+
+            /// Clears the vector, removing all values.
+            pub fn clear(&self) {
+                self.with_mut(|v| v.clear())
+            }
+
+            /// Extends the vector with the given iterator.
+            pub fn extend(&self, iter: impl IntoIterator<Item = T>) {
+                self.with_mut(|v| v.extend(iter))
+            }
+
+            /// Truncates the vector to the given length.
+            pub fn truncate(&self, len: usize) {
+                self.with_mut(|v| v.truncate(len))
+            }
+
+            /// Swaps two values in the vector.
+            pub fn swap_remove(&self, index: usize) -> T {
+                self.with_mut(|v| v.swap_remove(index))
+            }
+
+            /// Retains only the values that match the given predicate.
+            pub fn retain(&self, f: impl FnMut(&T) -> bool) {
+                self.with_mut(|v| v.retain(f))
+            }
+
+            /// Splits the vector into two at the given index.
+            pub fn split_off(&self, at: usize) -> Vec<T> {
+                self.with_mut(|v| v.split_off(at))
+            }
+        }
+
+        impl<T: 'static> $ty<Option<T>> {
+            /// Takes the value out of the Option.
+            pub fn take(&self) -> Option<T> {
+                self.with_mut(|v| v.take())
+            }
+
+            /// Replace the value in the Option.
+            pub fn replace(&self, value: T) -> Option<T> {
+                self.with_mut(|v| v.replace(value))
+            }
+
+            /// Gets the value out of the Option, or inserts the given value if the Option is empty.
+            pub fn get_or_insert(&self, default: T) -> Ref<'_, T> {
+                self.get_or_insert_with(|| default)
+            }
+
+            /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty.
+            pub fn get_or_insert_with(&self, default: impl FnOnce() -> T) -> Ref<'_, T> {
+                let borrow = self.read();
+                if borrow.is_none() {
+                    drop(borrow);
+                    self.with_mut(|v| *v = Some(default()));
+                    Ref::map(self.read(), |v| v.as_ref().unwrap())
+                } else {
+                    Ref::map(borrow, |v| v.as_ref().unwrap())
+                }
+            }
+        }
+    };
+}
+
+read_impls!(CopyValue);
+write_impls!(CopyValue);
+read_impls!(Signal);
+write_impls!(Signal);
+read_impls!(ReadOnlySignal);
+
+/// An iterator over the values of a `CopyValue<Vec<T>>`.
+pub struct CopyValueIterator<T: 'static> {
+    index: usize,
+    value: CopyValue<Vec<T>>,
+}
+
+impl<T: Clone> Iterator for CopyValueIterator<T> {
+    type Item = T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let index = self.index;
+        self.index += 1;
+        self.value.get(index).map(|v| v.clone())
+    }
+}
+
+impl<T: Clone + 'static> IntoIterator for CopyValue<Vec<T>> {
+    type IntoIter = CopyValueIterator<T>;
+
+    type Item = T;
+
+    fn into_iter(self) -> Self::IntoIter {
+        CopyValueIterator {
+            index: 0,
+            value: self,
+        }
+    }
+}
+
+impl<T: 'static> CopyValue<Vec<T>> {
+    /// Write to an element in the inner vector.
+    pub fn get_mut(&self, index: usize) -> Option<RefMut<'_, T>> {
+        RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok()
+    }
+}
+
+impl<T: 'static> CopyValue<Option<T>> {
+    /// Deref the inner value mutably.
+    pub fn as_mut(&self) -> Option<RefMut<'_, T>> {
+        RefMut::filter_map(self.write(), |v| v.as_mut()).ok()
+    }
+}
+
+/// An iterator over items in a `Signal<Vec<T>>`.
+pub struct SignalIterator<T: 'static> {
+    index: usize,
+    value: Signal<Vec<T>>,
+}
+
+impl<T: Clone> Iterator for SignalIterator<T> {
+    type Item = T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let index = self.index;
+        self.index += 1;
+        self.value.get(index).map(|v| v.clone())
+    }
+}
+
+impl<T: Clone + 'static> IntoIterator for Signal<Vec<T>> {
+    type IntoIter = SignalIterator<T>;
+
+    type Item = T;
+
+    fn into_iter(self) -> Self::IntoIter {
+        SignalIterator {
+            index: 0,
+            value: self,
+        }
+    }
+}
+
+impl<T: 'static> Signal<Vec<T>> {
+    /// Returns a reference to an element or `None` if out of bounds.
+    pub fn get_mut(&self, index: usize) -> Option<Write<'_, T, Vec<T>>> {
+        Write::filter_map(self.write(), |v| v.get_mut(index))
+    }
+}
+
+impl<T: 'static> Signal<Option<T>> {
+    /// Returns a reference to an element or `None` if out of bounds.
+    pub fn as_mut(&self) -> Option<Write<'_, T, Option<T>>> {
+        Write::filter_map(self.write(), |v| v.as_mut())
+    }
+}

+ 11 - 128
packages/signals/src/lib.rs

@@ -1,131 +1,14 @@
-use std::{
-    cell::{Ref, RefMut},
-    fmt::Display,
-    marker::PhantomData,
-    ops::{Add, Div, Mul, Sub},
-};
+#![doc = include_str!("../README.md")]
+#![warn(missing_docs)]
 
 
 mod rt;
 mod rt;
-
-use dioxus_core::ScopeState;
 pub use rt::*;
 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);
-    });
-}
-
-pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
-    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<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> {
-    id: usize,
-    rt: &'static SignalRt,
-    t: PhantomData<T>,
-}
-
-impl<T: 'static> Signal<T> {
-    pub fn read(&self) -> Ref<T> {
-        self.rt.read(self.id)
-    }
-
-    pub fn write(&self) -> RefMut<T> {
-        self.rt.write(self.id)
-    }
-
-    pub fn set(&mut self, value: T) {
-        self.rt.set(self.id, value);
-    }
-
-    pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
-        let write = self.read();
-        f(&*write)
-    }
-
-    pub fn update<O>(&self, _f: impl FnOnce(&mut T) -> O) -> O {
-        let mut write = self.write();
-        _f(&mut *write)
-    }
-}
-
-impl<T: Clone + 'static> Signal<T> {
-    pub fn get(&self) -> T {
-        self.rt.get(self.id)
-    }
-}
-
-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)
-    }
-}
-
-impl<T> std::clone::Clone for Signal<T> {
-    fn clone(&self) -> Self {
-        *self
-    }
-}
-
-impl<T> Copy for Signal<T> {}
-
-impl<T: Display + 'static> Display for Signal<T> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        self.rt.with::<T, _>(self.id, |v| T::fmt(v, f))
-    }
-}
-
-impl<T: Add<Output = T> + Copy + 'static> std::ops::AddAssign<T> for Signal<T> {
-    fn add_assign(&mut self, rhs: T) {
-        self.set(self.get() + rhs);
-    }
-}
-
-impl<T: Sub<Output = T> + Copy + 'static> std::ops::SubAssign<T> for Signal<T> {
-    fn sub_assign(&mut self, rhs: T) {
-        self.set(self.get() - rhs);
-    }
-}
-
-impl<T: Mul<Output = T> + Copy + 'static> std::ops::MulAssign<T> for Signal<T> {
-    fn mul_assign(&mut self, rhs: T) {
-        self.set(self.get() * rhs);
-    }
-}
-
-impl<T: Div<Output = T> + Copy + 'static> std::ops::DivAssign<T> for Signal<T> {
-    fn div_assign(&mut self, rhs: T) {
-        self.set(self.get() / rhs);
-    }
-}
+mod effect;
+pub use effect::*;
+mod impls;
+mod selector;
+pub use selector::*;
+pub(crate) mod signal;
+pub use signal::*;
+mod dependency;
+pub use dependency::*;

+ 120 - 82
packages/signals/src/rt.rs

@@ -1,121 +1,159 @@
-use std::{any::Any, cell::RefCell, sync::Arc};
+use std::cell::{Ref, RefMut};
 
 
+use std::rc::Rc;
+
+use dioxus_core::prelude::{
+    consume_context, consume_context_from_scope, current_scope_id, provide_context,
+    provide_context_to_scope, provide_root_context,
+};
 use dioxus_core::ScopeId;
 use dioxus_core::ScopeId;
-use slab::Slab;
 
 
-thread_local! {
-    // we cannot drop these since any future might be using them
-    static RUNTIMES: RefCell<Vec<&'static SignalRt>> = RefCell::new(Vec::new());
-}
+use generational_box::{GenerationalBox, Owner, Store};
 
 
-/// Provide the runtime for signals
-///
-/// This will reuse dead runtimes
-pub fn claim_rt(update_any: Arc<dyn Fn(ScopeId)>) -> &'static SignalRt {
-    RUNTIMES.with(|runtimes| {
-        if let Some(rt) = runtimes.borrow_mut().pop() {
-            return rt;
+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,
-        }))
-    })
+fn current_owner() -> Rc<Owner> {
+    match consume_context() {
+        Some(rt) => rt,
+        None => {
+            let owner = Rc::new(current_store().owner());
+            provide_context(owner).expect("in a virtual dom")
+        }
+    }
 }
 }
 
 
-/// Push this runtime into the global runtime list
-pub fn reclam_rt(_rt: &'static SignalRt) {
-    RUNTIMES.with(|runtimes| {
-        runtimes.borrow_mut().push(_rt);
-    });
+fn owner_in_scope(scope: ScopeId) -> Rc<Owner> {
+    match consume_context_from_scope(scope) {
+        Some(rt) => rt,
+        None => {
+            let owner = Rc::new(current_store().owner());
+            provide_context_to_scope(scope, owner).expect("in a virtual dom")
+        }
+    }
 }
 }
 
 
-pub struct SignalRt {
-    pub(crate) signals: RefCell<Slab<Inner>>,
-    pub(crate) update_any: Arc<dyn Fn(ScopeId)>,
+/// CopyValue is a wrapper around a value to make the value mutable and Copy.
+///
+/// It is internally backed by [`generational_box::GenerationalBox`].
+pub struct CopyValue<T: 'static> {
+    pub(crate) value: GenerationalBox<T>,
+    origin_scope: ScopeId,
 }
 }
 
 
-impl SignalRt {
-    pub fn init<T: 'static>(&'static self, val: T) -> usize {
-        self.signals.borrow_mut().insert(Inner {
-            value: Box::new(val),
-            subscribers: Vec::new(),
-            getter: None,
-        })
+#[cfg(feature = "serde")]
+impl<T: 'static> serde::Serialize for CopyValue<T>
+where
+    T: serde::Serialize,
+{
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        self.value.read().serialize(serializer)
     }
     }
+}
+
+#[cfg(feature = "serde")]
+impl<'de, T: 'static> serde::Deserialize<'de> for CopyValue<T>
+where
+    T: serde::Deserialize<'de>,
+{
+    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        let value = T::deserialize(deserializer)?;
 
 
-    pub fn subscribe(&self, id: usize, subscriber: ScopeId) {
-        self.signals.borrow_mut()[id].subscribers.push(subscriber);
+        Ok(Self::new(value))
     }
     }
+}
 
 
-    pub fn get<T: Clone + 'static>(&self, id: usize) -> T {
-        self.signals.borrow()[id]
-            .value
-            .downcast_ref::<T>()
-            .cloned()
-            .unwrap()
+impl<T: 'static> CopyValue<T> {
+    /// Create a new CopyValue. The value will be stored in the current component.
+    ///
+    /// Once the component this value is created in is dropped, the value will be dropped.
+    pub fn new(value: T) -> Self {
+        let owner = current_owner();
+
+        Self {
+            value: owner.insert(value),
+            origin_scope: current_scope_id().expect("in a virtual dom"),
+        }
     }
     }
 
 
-    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);
+    /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped.
+    pub fn new_in_scope(value: T, scope: ScopeId) -> Self {
+        let owner = owner_in_scope(scope);
 
 
-        for subscriber in inner.subscribers.iter() {
-            (self.update_any)(*subscriber);
+        Self {
+            value: owner.insert(value),
+            origin_scope: scope,
         }
         }
     }
     }
 
 
-    pub fn remove(&self, id: usize) {
-        self.signals.borrow_mut().remove(id);
+    pub(crate) fn invalid() -> Self {
+        let owner = current_owner();
+
+        Self {
+            value: owner.invalid(),
+            origin_scope: current_scope_id().expect("in a virtual dom"),
+        }
     }
     }
 
 
-    pub fn with<T: 'static, O>(&self, id: usize, f: impl FnOnce(&T) -> O) -> O {
-        let signals = self.signals.borrow();
-        let inner = &signals[id];
-        let inner = inner.value.downcast_ref::<T>().unwrap();
-        f(inner)
+    /// Get the scope this value was created in.
+    pub fn origin_scope(&self) -> ScopeId {
+        self.origin_scope
     }
     }
 
 
-    pub(crate) fn read<T: 'static>(&self, id: usize) -> std::cell::Ref<T> {
-        let signals = self.signals.borrow();
-        std::cell::Ref::map(signals, |signals| {
-            signals[id].value.downcast_ref::<T>().unwrap()
-        })
+    /// Try to read the value. If the value has been dropped, this will return None.
+    pub fn try_read(&self) -> Option<Ref<'_, T>> {
+        self.value.try_read()
     }
     }
 
 
-    pub(crate) fn write<T: 'static>(&self, id: usize) -> std::cell::RefMut<T> {
-        let signals = self.signals.borrow_mut();
-        std::cell::RefMut::map(signals, |signals| {
-            signals[id].value.downcast_mut::<T>().unwrap()
-        })
+    /// Read the value. If the value has been dropped, this will panic.
+    pub fn read(&self) -> Ref<'_, T> {
+        self.value.read()
     }
     }
 
 
-    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();
+    /// Try to write the value. If the value has been dropped, this will return None.
+    pub fn try_write(&self) -> Option<RefMut<'_, T>> {
+        self.value.try_write()
+    }
 
 
-        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) };
+    /// Write the value. If the value has been dropped, this will panic.
+    pub fn write(&self) -> RefMut<'_, T> {
+        self.value.write()
+    }
 
 
-            inner.getter = Some(getter);
-        }
+    /// Set the value. If the value has been dropped, this will panic.
+    pub fn set(&mut self, value: T) {
+        *self.write() = value;
+    }
 
 
-        let r = inner.getter.as_ref().unwrap();
+    /// Run a function with a reference to the value. If the value has been dropped, this will panic.
+    pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
+        let write = self.read();
+        f(&*write)
+    }
 
 
-        unsafe { std::mem::transmute::<&dyn Fn(), &dyn Fn() -> T>(r) }
+    /// Run a function with a mutable reference to the value. If the value has been dropped, this will panic.
+    pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
+        let mut write = self.write();
+        f(&mut *write)
     }
     }
 }
 }
 
 
-pub(crate) struct Inner {
-    pub value: Box<dyn Any>,
-    pub subscribers: Vec<ScopeId>,
+impl<T: Clone + 'static> CopyValue<T> {
+    /// Get the value. If the value has been dropped, this will panic.
+    pub fn value(&self) -> T {
+        self.read().clone()
+    }
+}
 
 
-    // todo: this has a soundness hole in it that you might not run into
-    pub getter: Option<Box<dyn Fn()>>,
+impl<T: 'static> PartialEq for CopyValue<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.value.ptr_eq(&other.value)
+    }
 }
 }

+ 105 - 0
packages/signals/src/selector.rs

@@ -0,0 +1,105 @@
+use dioxus_core::prelude::*;
+
+use crate::dependency::Dependency;
+use crate::use_signal;
+use crate::{get_effect_stack, signal::SignalData, CopyValue, Effect, ReadOnlySignal, Signal};
+
+/// Creates a new Selector. The selector will be run immediately and whenever any signal it reads changes.
+///
+/// Selectors can be used to efficiently compute derived data from signals.
+///
+/// ```rust
+/// use dioxus::prelude::*;
+/// use dioxus_signals::*;
+///
+/// fn App(cx: Scope) -> Element {
+///     let mut count = use_signal(cx, || 0);
+///     let double = use_selector(cx, move || count * 2);
+///     count += 1;
+///     assert_eq!(double.value(), count * 2);
+///  
+///     render! { "{double}" }
+/// }
+/// ```
+pub fn use_selector<R: PartialEq>(
+    cx: &ScopeState,
+    f: impl FnMut() -> R + 'static,
+) -> ReadOnlySignal<R> {
+    *cx.use_hook(|| selector(f))
+}
+
+/// Creates a new Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes
+///
+/// Selectors can be used to efficiently compute derived data from signals.
+///
+/// ```rust
+/// use dioxus::prelude::*;
+/// use dioxus_signals::*;
+///
+/// fn App(cx: Scope) -> Element {
+///     let mut local_state = use_state(cx, || 0);
+///     let double = use_selector_with_dependencies(cx, (local_state.get(),), move |(local_state,)| local_state * 2);
+///     local_state.set(1);
+///  
+///     render! { "{double}" }
+/// }
+/// ```
+pub fn use_selector_with_dependencies<R: PartialEq, D: Dependency>(
+    cx: &ScopeState,
+    dependencies: D,
+    mut f: impl FnMut(D::Out) -> R + 'static,
+) -> ReadOnlySignal<R>
+where
+    D::Out: 'static,
+{
+    let dependencies_signal = use_signal(cx, || dependencies.out());
+    let selector = *cx.use_hook(|| {
+        selector(move || {
+            let deref = &*dependencies_signal.read();
+            f(deref.clone())
+        })
+    });
+    let changed = { dependencies.changed(&*dependencies_signal.read()) };
+    if changed {
+        dependencies_signal.set(dependencies.out());
+    }
+    selector
+}
+
+/// Creates a new Selector. The selector will be run immediately and whenever any signal it reads changes.
+///
+/// Selectors can be used to efficiently compute derived data from signals.
+pub fn selector<R: PartialEq>(mut f: impl FnMut() -> R + 'static) -> ReadOnlySignal<R> {
+    let state = Signal::<R> {
+        inner: CopyValue::invalid(),
+    };
+    let effect = Effect {
+        callback: CopyValue::invalid(),
+    };
+
+    {
+        get_effect_stack().effects.borrow_mut().push(effect);
+    }
+    state.inner.value.set(SignalData {
+        subscribers: Default::default(),
+        effect_subscribers: Default::default(),
+        update_any: schedule_update_any().expect("in a virtual dom"),
+        value: f(),
+    });
+    {
+        get_effect_stack().effects.borrow_mut().pop();
+    }
+
+    effect.callback.value.set(Box::new(move || {
+        let value = f();
+        let changed = {
+            let old = state.inner.read();
+            value != old.value
+        };
+        if changed {
+            state.set(value)
+        }
+    }));
+
+    ReadOnlySignal::new(state)
+}

+ 344 - 0
packages/signals/src/signal.rs

@@ -0,0 +1,344 @@
+use std::{
+    cell::{Ref, RefCell, RefMut},
+    ops::{Deref, DerefMut},
+    rc::Rc,
+    sync::Arc,
+};
+
+use dioxus_core::{
+    prelude::{current_scope_id, has_context, provide_context, schedule_update_any},
+    ScopeId, ScopeState,
+};
+
+use crate::{CopyValue, Effect};
+
+/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
+///
+/// ```rust
+/// use dioxus::prelude::*;
+/// use dioxus_signals::*;
+///
+/// fn App(cx: Scope) -> Element {
+///     let mut count = use_signal(cx, || 0);
+///
+///     // Because signals have automatic dependency tracking, if you never read them in a component, that component will not be re-rended when the signal is updated.
+///     // The app component will never be rerendered in this example.
+///     render! { Child { state: count } }
+/// }
+///
+/// #[inline_props]
+/// fn Child(cx: Scope, state: Signal<u32>) -> Element {
+///     let state = *state;
+///
+///     use_future!(cx,  |()| async move {
+///         // Because the signal is a Copy type, we can use it in an async block without cloning it.
+///         *state.write() += 1;
+///     });
+///
+///     render! {
+///         button {
+///             onclick: move |_| *state.write() += 1,
+///             "{state}"
+///         }
+///     }
+/// }
+/// ```
+pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
+    *cx.use_hook(|| Signal::new(f()))
+}
+
+#[derive(Clone)]
+struct Unsubscriber {
+    scope: ScopeId,
+    subscribers: UnsubscriberArray,
+}
+
+type UnsubscriberArray = Rc<RefCell<Vec<Rc<RefCell<Vec<ScopeId>>>>>>;
+
+impl Drop for Unsubscriber {
+    fn drop(&mut self) {
+        for subscribers in self.subscribers.borrow().iter() {
+            subscribers.borrow_mut().retain(|s| *s != self.scope);
+        }
+    }
+}
+
+fn current_unsubscriber() -> Unsubscriber {
+    match has_context() {
+        Some(rt) => rt,
+        None => {
+            let owner = Unsubscriber {
+                scope: current_scope_id().expect("in a virtual dom"),
+                subscribers: Default::default(),
+            };
+            provide_context(owner).expect("in a virtual dom")
+        }
+    }
+}
+
+pub(crate) struct SignalData<T> {
+    pub(crate) subscribers: Rc<RefCell<Vec<ScopeId>>>,
+    pub(crate) effect_subscribers: Rc<RefCell<Vec<Effect>>>,
+    pub(crate) update_any: Arc<dyn Fn(ScopeId)>,
+    pub(crate) value: T,
+}
+
+/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
+///
+/// ```rust
+/// use dioxus::prelude::*;
+/// use dioxus_signals::*;
+///
+/// fn App(cx: Scope) -> Element {
+///     let mut count = use_signal(cx, || 0);
+///
+///     // Because signals have automatic dependency tracking, if you never read them in a component, that component will not be re-rended when the signal is updated.
+///     // The app component will never be rerendered in this example.
+///     render! { Child { state: count } }
+/// }
+///
+/// #[inline_props]
+/// fn Child(cx: Scope, state: Signal<u32>) -> Element {
+///     let state = *state;
+///
+///     use_future!(cx,  |()| async move {
+///         // Because the signal is a Copy type, we can use it in an async block without cloning it.
+///         *state.write() += 1;
+///     });
+///
+///     render! {
+///         button {
+///             onclick: move |_| *state.write() += 1,
+///             "{state}"
+///         }
+///     }
+/// }
+/// ```
+pub struct Signal<T: 'static> {
+    pub(crate) inner: CopyValue<SignalData<T>>,
+}
+
+#[cfg(feature = "serde")]
+impl<T: serde::Serialize + 'static> serde::Serialize for Signal<T> {
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        self.read().serialize(serializer)
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Signal<T> {
+    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        Ok(Self::new(T::deserialize(deserializer)?))
+    }
+}
+
+impl<T: 'static> Signal<T> {
+    /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
+    pub fn new(value: T) -> Self {
+        Self {
+            inner: CopyValue::new(SignalData {
+                subscribers: Default::default(),
+                effect_subscribers: Default::default(),
+                update_any: schedule_update_any().expect("in a virtual dom"),
+                value,
+            }),
+        }
+    }
+
+    /// Get the scope the signal was created in.
+    pub fn origin_scope(&self) -> ScopeId {
+        self.inner.origin_scope()
+    }
+
+    /// Get the current value of the signal. This will subscribe the current scope to the signal.
+    /// If the signal has been dropped, this will panic.
+    pub fn read(&self) -> Ref<T> {
+        let inner = self.inner.read();
+        if let Some(effect) = Effect::current() {
+            let mut effect_subscribers = inner.effect_subscribers.borrow_mut();
+            if !effect_subscribers.contains(&effect) {
+                effect_subscribers.push(effect);
+            }
+        } else if let Some(current_scope_id) = current_scope_id() {
+            // only subscribe if the vdom is rendering
+            if dioxus_core::vdom_is_rendering() {
+                log::trace!(
+                    "{:?} subscribed to {:?}",
+                    self.inner.value,
+                    current_scope_id
+                );
+                let mut subscribers = inner.subscribers.borrow_mut();
+                if !subscribers.contains(&current_scope_id) {
+                    subscribers.push(current_scope_id);
+                    drop(subscribers);
+                    let unsubscriber = current_unsubscriber();
+                    inner.subscribers.borrow_mut().push(unsubscriber.scope);
+                }
+            }
+        }
+        Ref::map(inner, |v| &v.value)
+    }
+
+    /// Get a mutable reference to the signal's value.
+    /// If the signal has been dropped, this will panic.
+    pub fn write(&self) -> Write<'_, T> {
+        let inner = self.inner.write();
+        let borrow = RefMut::map(inner, |v| &mut v.value);
+        Write {
+            write: borrow,
+            signal: SignalSubscriberDrop { signal: *self },
+        }
+    }
+
+    fn update_subscribers(&self) {
+        {
+            let inner = self.inner.read();
+            for &scope_id in &*inner.subscribers.borrow() {
+                log::trace!(
+                    "Write on {:?} triggered update on {:?}",
+                    self.inner.value,
+                    scope_id
+                );
+                (inner.update_any)(scope_id);
+            }
+        }
+
+        let subscribers = {
+            let self_read = self.inner.read();
+            let mut effects = self_read.effect_subscribers.borrow_mut();
+            std::mem::take(&mut *effects)
+        };
+        for effect in subscribers {
+            log::trace!(
+                "Write on {:?} triggered effect {:?}",
+                self.inner.value,
+                effect
+            );
+            effect.try_run();
+        }
+    }
+
+    /// Set the value of the signal. This will trigger an update on all subscribers.
+    pub fn set(&self, value: T) {
+        *self.write() = value;
+    }
+
+    /// Run a closure with a reference to the signal's value.
+    /// If the signal has been dropped, this will panic.
+    pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
+        let write = self.read();
+        f(&*write)
+    }
+
+    /// Run a closure with a mutable reference to the signal's value.
+    /// If the signal has been dropped, this will panic.
+    pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
+        let mut write = self.write();
+        f(&mut *write)
+    }
+}
+
+impl<T: Clone + 'static> Signal<T> {
+    /// Get the current value of the signal. This will subscribe the current scope to the signal.
+    /// If the signal has been dropped, this will panic.
+    pub fn value(&self) -> T {
+        self.read().clone()
+    }
+}
+
+impl<T: 'static> PartialEq for Signal<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.inner == other.inner
+    }
+}
+
+struct SignalSubscriberDrop<T: 'static> {
+    signal: Signal<T>,
+}
+
+impl<T: 'static> Drop for SignalSubscriberDrop<T> {
+    fn drop(&mut self) {
+        self.signal.update_subscribers();
+    }
+}
+
+/// A mutable reference to a signal's value.
+pub struct Write<'a, T: 'static, I: 'static = T> {
+    write: RefMut<'a, T>,
+    signal: SignalSubscriberDrop<I>,
+}
+
+impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
+    /// Map the mutable reference to the signal's value to a new type.
+    pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, I> {
+        let Self { write, signal } = myself;
+        Write {
+            write: RefMut::map(write, f),
+            signal,
+        }
+    }
+
+    /// Try to map the mutable reference to the signal's value to a new type
+    pub fn filter_map<O>(
+        myself: Self,
+        f: impl FnOnce(&mut T) -> Option<&mut O>,
+    ) -> Option<Write<'a, O, I>> {
+        let Self { write, signal } = myself;
+        let write = RefMut::filter_map(write, f).ok();
+        write.map(|write| Write { write, signal })
+    }
+}
+
+impl<'a, T: 'static> Deref for Write<'a, T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.write
+    }
+}
+
+impl<T> DerefMut for Write<'_, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.write
+    }
+}
+
+/// A signal that can only be read from.
+pub struct ReadOnlySignal<T: 'static> {
+    inner: Signal<T>,
+}
+
+impl<T: 'static> ReadOnlySignal<T> {
+    /// Create a new read-only signal.
+    pub fn new(signal: Signal<T>) -> Self {
+        Self { inner: signal }
+    }
+
+    /// Get the scope that the signal was created in.
+    pub fn origin_scope(&self) -> ScopeId {
+        self.inner.origin_scope()
+    }
+
+    /// Get the current value of the signal. This will subscribe the current scope to the signal.
+    pub fn read(&self) -> Ref<T> {
+        self.inner.read()
+    }
+
+    /// Run a closure with a reference to the signal's value.
+    pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
+        self.inner.with(f)
+    }
+}
+
+impl<T: Clone + 'static> ReadOnlySignal<T> {
+    /// Get the current value of the signal. This will subscribe the current scope to the signal.
+    pub fn value(&self) -> T {
+        self.read().clone()
+    }
+}
+
+impl<T: 'static> PartialEq for ReadOnlySignal<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.inner == other.inner
+    }
+}

+ 60 - 0
packages/signals/tests/create.rs

@@ -0,0 +1,60 @@
+#![allow(unused, non_upper_case_globals, non_snake_case)]
+
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+use dioxus_signals::*;
+
+#[test]
+fn create_signals_global() {
+    let mut dom = VirtualDom::new(|cx| {
+        render! {
+            for _ in 0..10 {
+                Child {}
+            }
+        }
+    });
+
+    fn Child(cx: Scope) -> Element {
+        let signal = create_without_cx();
+
+        render! {
+            "{signal}"
+        }
+    }
+
+    let _edits = dom.rebuild().santize();
+
+    fn create_without_cx() -> Signal<String> {
+        Signal::new("hello world".to_string())
+    }
+}
+
+#[test]
+fn drop_signals() {
+    let mut dom = VirtualDom::new(|cx| {
+        let generation = cx.generation();
+
+        let count = if generation % 2 == 0 { 10 } else { 0 };
+        render! {
+            for _ in 0..count {
+                Child {}
+            }
+        }
+    });
+
+    fn Child(cx: Scope) -> Element {
+        let signal = create_without_cx();
+
+        render! {
+            "{signal}"
+        }
+    }
+
+    let _ = dom.rebuild().santize();
+    dom.mark_dirty(ScopeId(0));
+    dom.render_immediate();
+
+    fn create_without_cx() -> Signal<String> {
+        Signal::new("hello world".to_string())
+    }
+}

+ 47 - 0
packages/signals/tests/effect.rs

@@ -0,0 +1,47 @@
+#![allow(unused, non_upper_case_globals, non_snake_case)]
+use std::collections::HashMap;
+use std::rc::Rc;
+
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+use dioxus_signals::*;
+
+#[test]
+fn effects_rerun() {
+    simple_logger::SimpleLogger::new().init().unwrap();
+
+    #[derive(Default)]
+    struct RunCounter {
+        component: usize,
+        effect: usize,
+    }
+
+    let counter = Rc::new(RefCell::new(RunCounter::default()));
+    let mut dom = VirtualDom::new_with_props(
+        |cx| {
+            let counter = cx.props;
+            counter.borrow_mut().component += 1;
+
+            let mut signal = use_signal(cx, || 0);
+            cx.use_hook(move || {
+                to_owned![counter];
+                Effect::new(move || {
+                    counter.borrow_mut().effect += 1;
+                    println!("Signal: {:?}", signal);
+                })
+            });
+            signal += 1;
+
+            render! {
+                div {}
+            }
+        },
+        counter.clone(),
+    );
+
+    let _ = dom.rebuild().santize();
+
+    let current_counter = counter.borrow();
+    assert_eq!(current_counter.component, 1);
+    assert_eq!(current_counter.effect, 2);
+}

+ 145 - 0
packages/signals/tests/selector.rs

@@ -0,0 +1,145 @@
+#![allow(unused, non_upper_case_globals, non_snake_case)]
+use std::collections::HashMap;
+use std::rc::Rc;
+
+use dioxus::html::p;
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+use dioxus_signals::*;
+
+#[test]
+fn memos_rerun() {
+    let _ = simple_logger::SimpleLogger::new().init();
+
+    #[derive(Default)]
+    struct RunCounter {
+        component: usize,
+        effect: usize,
+    }
+
+    let counter = Rc::new(RefCell::new(RunCounter::default()));
+    let mut dom = VirtualDom::new_with_props(
+        |cx| {
+            let counter = cx.props;
+            counter.borrow_mut().component += 1;
+
+            let mut signal = use_signal(cx, || 0);
+            let memo = cx.use_hook(move || {
+                to_owned![counter];
+                selector(move || {
+                    counter.borrow_mut().effect += 1;
+                    println!("Signal: {:?}", signal);
+                    signal.value()
+                })
+            });
+            assert_eq!(memo.value(), 0);
+            signal += 1;
+            assert_eq!(memo.value(), 1);
+
+            render! {
+                div {}
+            }
+        },
+        counter.clone(),
+    );
+
+    let _ = dom.rebuild().santize();
+
+    let current_counter = counter.borrow();
+    assert_eq!(current_counter.component, 1);
+    assert_eq!(current_counter.effect, 2);
+}
+
+#[test]
+fn memos_prevents_component_rerun() {
+    let _ = simple_logger::SimpleLogger::new().init();
+
+    #[derive(Default)]
+    struct RunCounter {
+        component: usize,
+        effect: usize,
+    }
+
+    let counter = Rc::new(RefCell::new(RunCounter::default()));
+    let mut dom = VirtualDom::new_with_props(
+        |cx| {
+            let mut signal = use_signal(cx, || 0);
+
+            if cx.generation() == 1 {
+                *signal.write() = 0;
+            }
+            if cx.generation() == 2 {
+                println!("Writing to signal");
+                *signal.write() = 1;
+            }
+
+            render! {
+                Child {
+                    signal: signal,
+                    counter: cx.props.clone(),
+                }
+            }
+        },
+        counter.clone(),
+    );
+
+    #[derive(Default, Props)]
+    struct ChildProps {
+        signal: Signal<usize>,
+        counter: Rc<RefCell<RunCounter>>,
+    }
+
+    impl PartialEq for ChildProps {
+        fn eq(&self, other: &Self) -> bool {
+            self.signal == other.signal
+        }
+    }
+
+    fn Child(cx: Scope<ChildProps>) -> Element {
+        let counter = &cx.props.counter;
+        let signal = cx.props.signal;
+        counter.borrow_mut().component += 1;
+
+        let memo = cx.use_hook(move || {
+            to_owned![counter];
+            selector(move || {
+                counter.borrow_mut().effect += 1;
+                println!("Signal: {:?}", signal);
+                signal.value()
+            })
+        });
+        match cx.generation() {
+            0 => {
+                assert_eq!(memo.value(), 0);
+            }
+            1 => {
+                assert_eq!(memo.value(), 1);
+            }
+            _ => panic!("Unexpected generation"),
+        }
+
+        render! {
+            div {}
+        }
+    }
+
+    let _ = dom.rebuild().santize();
+    dom.mark_dirty(ScopeId(0));
+    dom.render_immediate();
+
+    {
+        let current_counter = counter.borrow();
+        assert_eq!(current_counter.component, 1);
+        assert_eq!(current_counter.effect, 2);
+    }
+
+    dom.mark_dirty(ScopeId(0));
+    dom.render_immediate();
+    dom.render_immediate();
+
+    {
+        let current_counter = counter.borrow();
+        assert_eq!(current_counter.component, 2);
+        assert_eq!(current_counter.effect, 3);
+    }
+}

+ 92 - 0
packages/signals/tests/subscribe.rs

@@ -0,0 +1,92 @@
+#![allow(unused, non_upper_case_globals, non_snake_case)]
+use std::collections::HashMap;
+use std::rc::Rc;
+
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+use dioxus_signals::*;
+
+#[test]
+fn reading_subscribes() {
+    simple_logger::SimpleLogger::new().init().unwrap();
+
+    #[derive(Default)]
+    struct RunCounter {
+        parent: usize,
+        children: HashMap<ScopeId, usize>,
+    }
+
+    let counter = Rc::new(RefCell::new(RunCounter::default()));
+    let mut dom = VirtualDom::new_with_props(
+        |cx| {
+            let mut signal = use_signal(cx, || 0);
+
+            println!("Parent: {:?}", cx.scope_id());
+            if cx.generation() == 1 {
+                signal += 1;
+            }
+
+            cx.props.borrow_mut().parent += 1;
+
+            render! {
+                for id in 0..10 {
+                    Child {
+                        signal: signal,
+                        counter: cx.props.clone()
+                    }
+                }
+            }
+        },
+        counter.clone(),
+    );
+
+    #[derive(Props, Clone)]
+    struct ChildProps {
+        signal: Signal<usize>,
+        counter: Rc<RefCell<RunCounter>>,
+    }
+
+    impl PartialEq for ChildProps {
+        fn eq(&self, other: &Self) -> bool {
+            self.signal == other.signal
+        }
+    }
+
+    fn Child(cx: Scope<ChildProps>) -> Element {
+        println!("Child: {:?}", cx.scope_id());
+        *cx.props
+            .counter
+            .borrow_mut()
+            .children
+            .entry(cx.scope_id())
+            .or_default() += 1;
+
+        render! {
+            "{cx.props.signal}"
+        }
+    }
+
+    let _ = dom.rebuild().santize();
+
+    {
+        let current_counter = counter.borrow();
+        assert_eq!(current_counter.parent, 1);
+
+        for (scope_id, rerun_count) in current_counter.children.iter() {
+            assert_eq!(rerun_count, &1);
+        }
+    }
+
+    dom.mark_dirty(ScopeId(0));
+    dom.render_immediate();
+    dom.render_immediate();
+
+    {
+        let current_counter = counter.borrow();
+        assert_eq!(current_counter.parent, 2);
+
+        for (scope_id, rerun_count) in current_counter.children.iter() {
+            assert_eq!(rerun_count, &2);
+        }
+    }
+}