瀏覽代碼

Implement effects using reactivecontext

Jonathan Kelley 1 年之前
父節點
當前提交
7c2947a131

+ 1 - 1
Cargo.toml

@@ -68,7 +68,7 @@ dioxus-html-internal-macro = { path = "packages/html-internal-macro", version =
 dioxus-hooks = { path = "packages/hooks", version = "0.4.0" }
 dioxus-web = { path = "packages/web", version = "0.4.0", default-features = false  }
 dioxus-ssr = { path = "packages/ssr", version = "0.4.0", default-features = false  }
-dioxus-desktop = { path = "packages/desktop", version = "0.4.0", default-features = false }
+dioxus-desktop = { path = "packages/desktop", version = "0.4.0" }
 dioxus-mobile = { path = "packages/mobile", version = "0.4.0"  }
 dioxus-interpreter-js = { path = "packages/interpreter", version = "0.4.0" }
 dioxus-liveview = { path = "packages/liveview", version = "0.4.0"  }

+ 1 - 1
examples/eval.rs

@@ -22,7 +22,7 @@ fn app() -> Element {
         res
     });
 
-    match future.value().read().as_ref() {
+    match future.value().as_ref() {
         Some(v) => rsx!( p { "{v}" } ),
         _ => rsx!( p { "waiting.." } ),
     }

+ 15 - 0
examples/readme.rs

@@ -7,8 +7,23 @@ fn main() {
 fn app() -> Element {
     let mut count = use_signal(|| 0);
 
+    use_effect(move || {
+        println!("The count is now: {}", count);
+    });
+
     rsx! {
+        h1 { "High-Five counter: {count}" }
+        Child { sig: count }
         button { onclick: move |_| count += 1, "Up high!" }
         button { onclick: move |_| count -= 1, "Down low!" }
     }
 }
+
+#[component]
+fn Child(sig: Signal<i32>) -> Element {
+    let doubled = use_memo(move || sig() * 2);
+
+    rsx! {
+        "The count is: {sig}, doubled: {doubled}"
+    }
+}

+ 0 - 59
examples/server_future.rs

@@ -1,59 +0,0 @@
-use dioxus::prelude::*;
-
-fn main() {
-    launch(app);
-}
-
-fn app() -> Element {
-    let val = use_server_future(fetch_users).suspend()?;
-
-    rsx! {
-        h1 { "Users" }
-
-    }
-}
-
-#[component]
-fn ClientComponent(name: Signal<i32>, id: i64) -> Element {
-    rsx! {
-        div { "Name: {name}, ID: {id}" }
-        button {
-            onclick: move |_| async move {
-                // Optimistically change the name on the client
-                name.set("new name".to_string());
-
-                // Change the name on the server
-                change_name(id, "new name".to_string()).await;
-
-                // And then re-fetch the user list
-                revalidate(user_list);
-            },
-            "Change name"
-        }
-    }
-}
-
-#[derive(Table)]
-struct Users {
-    name: String,
-    age: i32,
-}
-
-#[server]
-async fn fetch_users() -> Result<Element> {
-    let users = get_users().await?;
-
-    Ok(rsx! {
-        for user in users {
-            ClientComponent {
-                name: user.name,
-                id: user.id,
-            }
-        }
-    })
-}
-
-#[server]
-async fn change_name(id: i64, new_name: String) -> Result<()> {
-    // Send a request to the server to change the name
-}

+ 2 - 0
examples/signals.rs

@@ -77,6 +77,8 @@ fn app() -> Element {
 
 #[component]
 fn Child(mut count: ReadOnlySignal<i32>) -> Element {
+    println!("rendering child with count {count}");
+
     rsx! {
         h1 { "{count}" }
     }

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

@@ -111,6 +111,11 @@ pub fn needs_update() {
     Runtime::with_current_scope(|cx| cx.needs_update());
 }
 
+/// Mark the current scope as dirty, causing it to re-render
+pub fn needs_update_any(id: ScopeId) {
+    Runtime::with_current_scope(|cx| cx.needs_update_any(id));
+}
+
 /// Schedule an update for the current component
 ///
 /// Note: Unlike [`needs_update`], the function returned by this method will work outside of the dioxus runtime.
@@ -252,6 +257,10 @@ pub fn after_render(f: impl FnMut() + 'static) {
 /// Effects rely on this to ensure that they only run effects after the DOM has been updated. Without flush_sync effects
 /// are run immediately before diffing the DOM, which causes all sorts of out-of-sync weirdness.
 pub async fn flush_sync() {
+    // if flushing() {
+    //      return;
+    // }
+
     _ = Runtime::with(|rt| rt.flush.clone())
         .unwrap()
         .recv_async()

+ 8 - 8
packages/core/src/lib.rs

@@ -88,13 +88,13 @@ pub use crate::innerlude::{
 pub mod prelude {
     pub use crate::innerlude::{
         consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, flush_sync,
-        generation, has_context, needs_update, parent_scope, provide_context, provide_root_context,
-        remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, suspend,
-        try_consume_context, use_after_render, use_before_render, use_drop, use_error_boundary,
-        use_hook, use_hook_with_cleanup, AnyValue, Attribute, Component, ComponentFunction,
-        Element, ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
-        IntoDynNode, OptionStringFromMarker, Properties, Runtime, RuntimeGuard, ScopeId,
-        ScopeState, SuperFrom, SuperInto, Task, Template, TemplateAttribute, TemplateNode, Throw,
-        VNode, VNodeInner, VirtualDom,
+        generation, has_context, needs_update, needs_update_any, parent_scope, provide_context,
+        provide_root_context, remove_future, schedule_update, schedule_update_any, spawn,
+        spawn_forever, suspend, try_consume_context, use_after_render, use_before_render, use_drop,
+        use_error_boundary, use_hook, use_hook_with_cleanup, AnyValue, Attribute, Component,
+        ComponentFunction, Element, ErrorBoundary, Event, EventHandler, Fragment, HasAttributes,
+        IntoAttributeValue, IntoDynNode, OptionStringFromMarker, Properties, Runtime, RuntimeGuard,
+        ScopeId, ScopeState, SuperFrom, SuperInto, Task, Template, TemplateAttribute, TemplateNode,
+        Throw, VNode, VNodeInner, VirtualDom,
     };
 }

+ 6 - 1
packages/core/src/scope_context.rs

@@ -60,8 +60,13 @@ impl Scope {
 
     /// Mark this scope as dirty, and schedule a render for it.
     pub fn needs_update(&self) {
+        self.needs_update_any(self.id)
+    }
+
+    /// Mark this scope as dirty, and schedule a render for it.
+    pub fn needs_update_any(&self, id: ScopeId) {
         self.sender()
-            .unbounded_send(SchedulerMsg::Immediate(self.id))
+            .unbounded_send(SchedulerMsg::Immediate(id))
             .expect("Scheduler to exist if scope exists");
     }
 

+ 12 - 8
packages/core/src/virtual_dom.rs

@@ -372,11 +372,15 @@ impl VirtualDom {
     ///
     /// Whenever the Runtime "works", it will re-render this scope
     pub fn mark_dirty(&mut self, id: ScopeId) {
-        if let Some(context) = self.runtime.get_state(id) {
-            let height = context.height();
-            tracing::trace!("Marking scope {:?} ({}) as dirty", id, context.name);
-            self.dirty_scopes.insert(DirtyScope { height, id });
-        }
+        let Some(scope) = self.runtime.get_state(id) else {
+            return;
+        };
+
+        tracing::trace!("Marking scope {:?} ({}) as dirty", id, scope.name);
+        self.dirty_scopes.insert(DirtyScope {
+            height: scope.height(),
+            id,
+        });
     }
 
     /// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an element with a listener, not a static node or a text node.**
@@ -422,9 +426,6 @@ impl VirtualDom {
     /// let dom = VirtualDom::new(app);
     /// ```
     pub async fn wait_for_work(&mut self) {
-        // Send the flush signal to the runtime
-        _ = self.flush_tx.try_send(());
-
         // And then poll the futures
         self.poll_tasks().await;
     }
@@ -432,6 +433,9 @@ impl VirtualDom {
     /// Poll futures without progressing any futures from the flush table
     async fn poll_tasks(&mut self) {
         loop {
+            // Send the flush signal to the runtime, waking up tasks
+            _ = self.flush_tx.try_send(());
+
             // Process all events - Scopes are marked dirty, etc
             // Sometimes when wakers fire we get a slew of updates at once, so its important that we drain this completely
             self.process_events();

+ 2 - 3
packages/fullstack/examples/salvo-hello-world/src/main.rs

@@ -16,9 +16,7 @@ fn app() -> Element {
     let mut text = use_signal(|| "...".to_string());
 
     rsx! {
-        div {
-            "Server state: {state.unwrap().value().clone()}"
-        }
+        div { "Server state: {state.value().unwrap()}" }
         h1 { "High-Five counter: {count}" }
         button { onclick: move |_| count += 1, "Up high!" }
         button { onclick: move |_| count -= 1, "Down low!" }
@@ -51,6 +49,7 @@ async fn get_server_data() -> Result<String, ServerFnError> {
 fn main() {
     #[cfg(feature = "web")]
     tracing_wasm::set_as_global_default();
+
     #[cfg(feature = "ssr")]
     tracing_subscriber::fmt::init();
 

+ 31 - 30
packages/fullstack/src/hooks/server_future.rs

@@ -62,37 +62,38 @@ pub struct UseServerFuture<T: 'static> {
     value: Signal<Option<Signal<T>>>,
 }
 
-// impl<T> UseServerFuture<T> {
-//     /// Restart the future with new dependencies.
-//     ///
-//     /// Will not cancel the previous future, but will ignore any values that it
-//     /// generates.
-//     pub fn restart(&self) {
-//         self.needs_regen.set(true);
-//         (self.update)();
-//     }
+impl<T> UseServerFuture<T> {
+    //     /// Restart the future with new dependencies.
+    //     ///
+    //     /// Will not cancel the previous future, but will ignore any values that it
+    //     /// generates.
+    //     pub fn restart(&self) {
+    //         self.needs_regen.set(true);
+    //         (self.update)();
+    //     }
 
-//     /// Forcefully cancel a future
-//     pub fn cancel(&self) {
-//         if let Some(task) = self.task.take() {
-//             remove_future(task);
-//         }
-//     }
+    //     /// Forcefully cancel a future
+    //     pub fn cancel(&self) {
+    //         if let Some(task) = self.task.take() {
+    //             remove_future(task);
+    //         }
+    //     }
 
-//     /// Return any value, even old values if the future has not yet resolved.
-//     ///
-//     /// If the future has never completed, the returned value will be `None`.
-//     pub fn value(&self) -> Ref<'_, T> {
-//         Ref::map(self.value.borrow(), |v| v.as_deref().unwrap())
-//     }
+    /// Return any value, even old values if the future has not yet resolved.
+    ///
+    /// If the future has never completed, the returned value will be `None`.
+    pub fn value(&self) -> Option<Signal<T>> {
+        todo!()
+        // Ref::map(self.value.read(), |v: &Option<T>| v.as_deref().unwrap())
+    }
 
-//     /// Get the ID of the future in Dioxus' internal scheduler
-//     pub fn task(&self) -> Option<Task> {
-//         self.task.get()
-//     }
+    //     /// Get the ID of the future in Dioxus' internal scheduler
+    //     pub fn task(&self) -> Option<Task> {
+    //         self.task.get()
+    //     }
 
-//     /// Get the current state of the future.
-//     pub fn reloading(&self) -> bool {
-//         self.task.get().is_some()
-//     }
-// }
+    //     /// Get the current state of the future.
+    //     pub fn reloading(&self) -> bool {
+    //         self.task.get().is_some()
+    //     }
+}

+ 14 - 11
packages/hooks/src/use_effect.rs

@@ -1,24 +1,27 @@
 use crate::use_hook_did_run;
 use dioxus_core::prelude::*;
-use dioxus_signals::{CopyValue, Effect, Writable};
+use dioxus_signals::{CopyValue, Effect, ReactiveContext, Writable};
+use futures_util::select;
 
 /// 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.
 ///
 /// If the use_effect call was skipped due to an early return, the effect will no longer activate.
 pub fn use_effect(mut callback: impl FnMut() + 'static) {
-    let mut run_effect = use_hook(|| CopyValue::new(true));
-
-    use_hook_did_run(move |did_run| match did_run {
-        true => run_effect.set(true),
-        false => run_effect.set(false),
-    });
+    // let mut run_effect = use_hook(|| CopyValue::new(true));
+    // use_hook_did_run(move |did_run| run_effect.set(did_run));
 
     use_hook(|| {
-        Effect::new(move || {
-            if run_effect() {
-                callback();
+        spawn(async move {
+            let rc = ReactiveContext::new(None);
+
+            loop {
+                // Run the effect
+                rc.run_in(|| callback());
+
+                // Wait for context to change
+                rc.changed().await;
             }
-        })
+        });
     });
 }

+ 0 - 5
packages/hooks/src/use_future.rs

@@ -21,11 +21,6 @@ where
     let mut callback = use_callback(move || {
         let fut = future();
         spawn(async move {
-            // todo: not sure if we should flush_sync
-            // It's fine for most cases but means that the future will always be started in the next frame
-            // The point here is to not run use_future on the server... which like, shouldn't we?
-            flush_sync().await;
-
             state.set(UseFutureState::Pending);
             fut.await;
             state.set(UseFutureState::Complete);

+ 27 - 3
packages/hooks/src/use_memo.rs

@@ -1,7 +1,7 @@
 use crate::dependency::Dependency;
 use crate::use_signal;
 use dioxus_core::prelude::*;
-use dioxus_signals::{ReadOnlySignal, Readable, Signal, SignalData};
+use dioxus_signals::{ReactiveContext, ReadOnlySignal, Readable, Signal, SignalData};
 use dioxus_signals::{Storage, Writable};
 
 /// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes.
@@ -45,9 +45,33 @@ pub fn use_memo<R: PartialEq>(f: impl FnMut() -> R + 'static) -> ReadOnlySignal<
 /// ```
 #[track_caller]
 pub fn use_maybe_sync_memo<R: PartialEq, S: Storage<SignalData<R>>>(
-    f: impl FnMut() -> R + 'static,
+    mut f: impl FnMut() -> R + 'static,
 ) -> ReadOnlySignal<R, S> {
-    use_hook(|| Signal::maybe_sync_memo(f))
+    use_hook(|| {
+        // Get the current reactive context
+        let rc = ReactiveContext::new(None);
+
+        // Create a new signal in that context, wiring up its dependencies and subscribers
+        let mut state: Signal<R, S> = rc.run_in(|| Signal::new_maybe_sync(f()));
+
+        spawn(async move {
+            loop {
+                rc.changed().await;
+
+                println!("change triggered in memo");
+
+                let new = rc.run_in(|| f());
+
+                if new != *state.peek() {
+                    println!("change sett in memo");
+                    *state.write() = new;
+                }
+            }
+        });
+
+        // And just return the readonly variant of that signal
+        ReadOnlySignal::new_maybe_sync(state)
+    })
 }
 
 /// Creates a new unsync Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes

+ 14 - 14
packages/hooks/src/use_resource.rs

@@ -27,20 +27,20 @@ where
 
         // Spawn a wrapper task that polls the innner future and watch its dependencies
         let task = spawn(async move {
-            // move the future here and pin it so we can poll it
-            let fut = fut;
-            pin_mut!(fut);
-
-            let res = future::poll_fn(|cx| {
-                // Set the effect stack properly
-                // add any dependencies to the effect stack that we need to watch when restarting the future
-                // Poll the inner future
-                fut.poll_unpin(cx)
-            })
-            .await;
-
-            // Set the value
-            value.set(Some(Signal::new(res)));
+            // // move the future here and pin it so we can poll it
+            // let fut = fut;
+            // pin_mut!(fut);
+
+            // let res = future::poll_fn(|cx| {
+            //     // Set the effect stack properly
+            //     // add any dependencies to the effect stack that we need to watch when restarting the future
+            //     // Poll the inner future
+            //     fut.poll_unpin(cx)
+            // })
+            // .await;
+
+            // // Set the value
+            // value.set(Some(Signal::new(res)));
         });
 
         Some(task)

+ 1 - 1
packages/hooks/src/use_signal.rs

@@ -96,7 +96,7 @@ fn use_maybe_signal_sync<T: 'static, U: Storage<SignalData<T>>>(
 
     // By default, we want to unsubscribe the current component from the signal on every render
     // any calls to .read() in the body will re-subscribe the component to the signal
-    use_before_render(move || signal.unsubscribe(current_scope_id().unwrap()));
+    // use_before_render(move || signal.unsubscribe(current_scope_id().unwrap()));
 
     signal
 }

+ 5 - 0
packages/html/src/eval.rs

@@ -91,10 +91,15 @@ impl IntoFuture for UseEval {
 /// Represents an error when evaluating JavaScript
 #[derive(Debug)]
 pub enum EvalError {
+    /// The platform does not support evaluating JavaScript.
+    Unspported,
+
     /// The provided JavaScript has already been ran.
     Finished,
+
     /// The provided JavaScript is not valid and can't be ran.
     InvalidJs(String),
+
     /// Represents an error communicating between JavaScript and Rust.
     Communication(String),
 }

+ 3 - 0
packages/signals/src/lib.rs

@@ -36,3 +36,6 @@ pub use write::*;
 
 mod props;
 pub use props::*;
+
+mod rc;
+pub use rc::*;

+ 93 - 24
packages/signals/src/rc.rs

@@ -1,9 +1,13 @@
-use dioxus_core::prelude::ScopeId;
+use dioxus_core::prelude::{
+    consume_context, consume_context_from_scope, current_scope_id, has_context, needs_update_any,
+    provide_context, schedule_update, schedule_update_any, try_consume_context, ScopeId,
+};
 use generational_box::{GenerationalBoxId, SyncStorage};
-use rustc_hash::FxHashSet;
+use rustc_hash::{FxHashMap, FxHashSet};
 use std::{cell::RefCell, hash::Hash};
+use tokio::signal;
 
-use crate::{CopyValue, Readable};
+use crate::{CopyValue, RcList, Readable, Writable};
 
 /// A context for signal reads and writes to be directed to
 ///
@@ -11,10 +15,9 @@ use crate::{CopyValue, Readable};
 /// If it doesn't find it, then it will try and insert a context into the nearest component scope via context api.
 ///
 /// When the ReactiveContext drops, it will remove itself from the the associated contexts attached to signal
-#[derive(Clone, Copy, PartialEq)]
+#[derive(Clone, Copy, PartialEq, Eq)]
 pub struct ReactiveContext {
-    // todo: we dont need to use syncstorage per say
-    inner: CopyValue<Inner, SyncStorage>,
+    pub inner: CopyValue<Inner, SyncStorage>,
 }
 
 thread_local! {
@@ -22,27 +25,94 @@ thread_local! {
 }
 
 impl ReactiveContext {
+    pub fn new(scope: Option<ScopeId>) -> Self {
+        let (tx, rx) = flume::unbounded();
+
+        let mut scope_subscribers = FxHashSet::default();
+        if let Some(scope) = scope {
+            scope_subscribers.insert(scope);
+        }
+
+        let inner = Inner {
+            signal_subscribers: FxHashMap::default(),
+            scope_subscribers,
+            sender: tx,
+            _self: None,
+            receiver: rx,
+        };
+
+        let mut _self = Self {
+            inner: CopyValue::new_maybe_sync(inner),
+        };
+
+        _self.inner.write()._self = Some(_self);
+
+        _self
+    }
+
     /// Get the current reactive context
     ///
     /// If this was set manually, then that value will be returned.
     ///
     /// If there's no current reactive context, then a new one will be created at the current scope and returned.
-    pub fn current() -> Self {
-        todo!()
+    pub fn current() -> Option<Self> {
+        let cur = CURRENT.with(|current| current.borrow().last().cloned());
+
+        // If we're already inside a reactive context, then return that
+        if let Some(cur) = cur {
+            println!("Already found context!");
+            return Some(cur);
+        }
+
+        // Try and get the context out of the current scope
+        let scope = current_scope_id().unwrap();
+
+        // If we're rendering, then try and use the reactive context attached to this component
+
+        if let Some(cx) = has_context() {
+            println!("found context at {scope:?}");
+            return Some(cx);
+        }
+
+        println!("creating new context at {scope:?}");
+
+        // Otherwise, create a new context at the current scope
+        Some(provide_context(ReactiveContext::new(Some(scope))))
     }
 
     /// Run this function in the context of this reactive context
     ///
     /// This will set the current reactive context to this context for the duration of the function.
     /// You can then get information about the current subscriptions.
-    pub fn run_in(&self, f: impl FnOnce()) {
-        todo!()
+    pub fn run_in<O>(&self, f: impl FnOnce() -> O) -> O {
+        CURRENT.with(|current| current.borrow_mut().push(*self));
+        let out = f();
+        CURRENT.with(|current| current.borrow_mut().pop());
+        out
     }
 
     /// Marks this reactive context as dirty
     ///
     /// If there's a scope associated with this context, then it will be marked as dirty too
-    pub fn mark_dirty(&self) {}
+    pub fn mark_dirty(&self) {
+        for scope in self.inner.read().scope_subscribers.iter() {
+            println!("marking dirty {scope:?}");
+            needs_update_any(*scope);
+        }
+
+        // mark the listeners as dirty
+        // If the channel is full it means that the receivers have already been marked as dirty
+        _ = self.inner.read().sender.try_send(());
+    }
+
+    // Create a two-way binding between this reactive context and a signal
+    pub fn link(&self, signal: GenerationalBoxId, rc_list: RcList) {
+        rc_list.write().insert(*self);
+        self.inner
+            .write()
+            .signal_subscribers
+            .insert(signal, rc_list);
+    }
 
     /// Clear all subscribers from this reactive context
     pub fn clear_subscribers(&self) {
@@ -51,9 +121,8 @@ impl ReactiveContext {
 
     /// Wait for this reactive context to change
     pub async fn changed(&self) {
-        let waiter = self.inner.read().waiters.1.clone();
-
-        _ = waiter.recv_async().await;
+        let rx = self.inner.read().receiver.clone();
+        _ = rx.recv_async().await;
     }
 }
 
@@ -63,22 +132,22 @@ impl Hash for ReactiveContext {
     }
 }
 
-struct Inner {
+pub struct Inner {
     // Set of signals bound to this context
-    subscribers: FxHashSet<GenerationalBoxId>,
-
-    // The scope that this context is associated with
-    // This is only relevant when RC is being used to call update on a signal
-    scope: Option<ScopeId>,
+    pub signal_subscribers: FxHashMap<GenerationalBoxId, RcList>,
+    scope_subscribers: FxHashSet<ScopeId>,
+    _self: Option<ReactiveContext>,
 
     // Futures will call .changed().await
-    waiters: (flume::Sender<()>, flume::Receiver<()>),
+    sender: flume::Sender<()>,
+    receiver: flume::Receiver<()>,
 }
 
-impl Inner {}
-
 impl Drop for Inner {
     fn drop(&mut self) {
-        todo!()
+        // Remove this context from all the subscribers
+        self.signal_subscribers.values().for_each(|sub_list| {
+            sub_list.write().remove(&self._self.unwrap());
+        });
     }
 }

+ 1 - 0
packages/signals/src/rt.rs

@@ -284,6 +284,7 @@ impl<T: 'static, S: Storage<T>> PartialEq for CopyValue<T, S> {
         self.value.ptr_eq(&other.value)
     }
 }
+impl<T: 'static, S: Storage<T>> Eq for CopyValue<T, S> {}
 
 impl<T: Copy, S: Storage<T>> Deref for CopyValue<T, S> {
     type Target = dyn Fn() -> T;

+ 44 - 153
packages/signals/src/signal.rs

@@ -1,11 +1,12 @@
 use crate::{
     get_effect_ref, read::Readable, write::Writable, CopyValue, Effect, EffectInner,
-    EffectStackRef, GlobalMemo, GlobalSignal, MappedSignal, ReactiveContext,
-    ReactiveContextProvider, ReadSignal, EFFECT_STACK,
+    EffectStackRef, GlobalMemo, GlobalSignal, MappedSignal, ReactiveContext, ReadOnlySignal,
+    EFFECT_STACK,
 };
 use dioxus_core::{
     prelude::{
-        current_scope_id, has_context, provide_context, schedule_update_any, IntoAttributeValue,
+        current_scope_id, has_context, provide_context, schedule_update_any, spawn,
+        IntoAttributeValue,
     },
     ScopeId,
 };
@@ -59,21 +60,16 @@ pub struct Signal<T: 'static, S: Storage<SignalData<T>> = UnsyncStorage> {
 /// A signal that can safely shared between threads.
 pub type SyncSignal<T> = Signal<T, SyncStorage>;
 
+/// The list of reactive contexts this signal is assocaited with
+/// Whenever we call .write() on the signal, these contexts will be notified of the change
+pub type RcList = Arc<RwLock<HashSet<ReactiveContext>>>;
+
 /// The data stored for tracking in a signal.
 pub struct SignalData<T> {
-    pub(crate) subscribers: Arc<RwLock<HashSet<ReactiveContext>>>,
-    pub(crate) update_any: Arc<dyn Fn(ScopeId) + Sync + Send>,
-    pub(crate) effect_ref: EffectStackRef,
+    pub(crate) subscribers: RcList,
     pub(crate) value: T,
 }
 
-// #[derive(Default)]
-// pub(crate) struct SignalSubscribers {
-//     // todo: use a linear map here for faster scans
-//     pub(crate) subscribers: FxHashSet<ScopeId>,
-//     pub(crate) effect_subscribers: FxHashSet<GenerationalBoxId>,
-// }
-
 impl<T: 'static> Signal<T> {
     /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
     #[track_caller]
@@ -97,10 +93,7 @@ impl<T: 'static> Signal<T> {
 impl<T: PartialEq + 'static> Signal<T> {
     /// Creates a new global Signal that can be used in a global static.
     #[track_caller]
-    pub const fn global_memo(constructor: fn() -> T) -> GlobalMemo<T>
-    where
-        T: PartialEq,
-    {
+    pub const fn global_memo(constructor: fn() -> T) -> GlobalMemo<T> {
         GlobalMemo::new(constructor)
     }
 
@@ -119,38 +112,26 @@ impl<T: PartialEq + 'static> Signal<T> {
     pub fn maybe_sync_memo<S: Storage<SignalData<T>>>(
         mut f: impl FnMut() -> T + 'static,
     ) -> ReadOnlySignal<T, S> {
-        let effect = Effect {
-            source: current_scope_id().expect("in a virtual dom"),
-            inner: CopyValue::invalid(),
-        };
+        // Get the current reactive context
+        let rc = ReactiveContext::current()
+            .expect("Cannot use a selector outside of a reactive context");
 
-        {
-            EFFECT_STACK.with(|stack| stack.effects.write().push(effect));
-        }
-        let mut state: Signal<T, S> = Signal::new_maybe_sync(f());
-        {
-            EFFECT_STACK.with(|stack| stack.effects.write().pop());
-        }
+        // Create a new signal in that context, wiring up its dependencies and subscribers
+        let mut state: Signal<T, S> = rc.run_in(|| Signal::new_maybe_sync(f()));
+
+        spawn(async move {
+            loop {
+                rc.changed().await;
+                println!("changed");
 
-        let invalid_id = effect.id();
-        tracing::trace!("Creating effect: {:?}", invalid_id);
-        effect.inner.value.set(EffectInner {
-            callback: Box::new(move || {
-                let value = f();
-                let changed = {
-                    let old = state.inner.read();
-                    value != old.value
-                };
-                if changed {
-                    state.set(value)
+                let new = f();
+                if new != *state.peek() {
+                    *state.write() = new;
                 }
-            }),
-            id: invalid_id,
+            }
         });
-        {
-            EFFECT_STACK.with(|stack| stack.effect_mapping.write().insert(invalid_id, effect));
-        }
 
+        // And just return the readonly variant of that signal
         ReadOnlySignal::new_maybe_sync(state)
     }
 }
@@ -163,9 +144,7 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
         Self {
             inner: CopyValue::<SignalData<T>, S>::new_maybe_sync(SignalData {
                 subscribers: Default::default(),
-                update_any: schedule_update_any(),
                 value,
-                effect_ref: get_effect_ref(),
             }),
         }
     }
@@ -179,9 +158,7 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
             inner: CopyValue::new_with_caller(
                 SignalData {
                     subscribers: Default::default(),
-                    update_any: schedule_update_any(),
                     value,
-                    effect_ref: get_effect_ref(),
                 },
                 #[cfg(debug_assertions)]
                 caller,
@@ -197,9 +174,7 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
             inner: CopyValue::<SignalData<T>, S>::new_maybe_sync_in_scope(
                 SignalData {
                     subscribers: Default::default(),
-                    update_any: schedule_update_any(),
                     value,
-                    effect_ref: get_effect_ref(),
                 },
                 owner,
             ),
@@ -217,44 +192,13 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
     }
 
     fn update_subscribers(&self) {
-        todo!()
-        // {
-        //     let inner = self.inner.read();
-        //     for &scope_id in inner.subscribers.read().subscribers.iter() {
-        //         tracing::trace!(
-        //             "Write on {:?} triggered update on {:?}",
-        //             self.inner.value,
-        //             scope_id
-        //         );
-        //         (inner.update_any)(scope_id);
-        //     }
-        // }
-
-        // let self_read = &self.inner.read();
-        // let subscribers = {
-        //     let effects = &mut self_read.subscribers.write().effect_subscribers;
-        //     std::mem::take(&mut *effects)
-        // };
-        // let effect_ref = &self_read.effect_ref;
-        // for effect in subscribers {
-        //     tracing::trace!(
-        //         "Write on {:?} triggered effect {:?}",
-        //         self.inner.value,
-        //         effect
-        //     );
-        //     effect_ref.queue_effect(effect);
-        // }
-    }
+        {
+            let inner = self.inner.read();
 
-    /// Unsubscribe this scope from the signal's effect list
-    pub fn unsubscribe(&self, scope: ScopeId) {
-        todo!()
-        // self.inner
-        //     .read()
-        //     .subscribers
-        //     .write()
-        //     .subscribers
-        //     .retain(|s| *s != scope);
+            for cx in inner.subscribers.read().iter() {
+                cx.mark_dirty();
+            }
+        }
     }
 
     /// Map the signal to a new type.
@@ -290,34 +234,9 @@ impl<T, S: Storage<SignalData<T>>> Readable<T> for Signal<T, S> {
     fn read(&self) -> S::Ref<T> {
         let inner = self.inner.read();
 
-        todo!();
-
-        // // if we're in a reactive context, attach the context to the signal via the mapping
-        // if let Some(effect) = ReactiveContextProvider::current() {
-        //     inner
-        //         .subscribers
-        //         .write()
-        //         .effect_subscribers
-        //         .insert(effect.inner.id());
-        // } else if let Some(current_scope_id) = current_scope_id() {
-        //     // only subscribe if the vdom is rendering
-        //     if dioxus_core::vdom_is_rendering() {
-        //         tracing::trace!(
-        //             "{:?} subscribed to {:?}",
-        //             self.inner.value,
-        //             current_scope_id
-        //         );
-
-        //         let mut subscribers = inner.subscribers.write();
-
-        //         if !subscribers.subscribers.contains(&current_scope_id) {
-        //             subscribers.subscribers.insert(current_scope_id);
-        //             subscribers
-        //                 .subscribers
-        //                 .insert(current_unsubscriber().borrow().scope);
-        //         }
-        //     }
-        // }
+        if let Some(mut cx) = ReactiveContext::current() {
+            cx.link(self.id(), inner.subscribers.clone());
+        }
 
         S::map(inner, |v| &v.value)
     }
@@ -404,44 +323,6 @@ impl<'de, T: serde::Deserialize<'de> + 'static, Store: Storage<SignalData<T>>>
     }
 }
 
-struct Unsubscriber {
-    scope: ScopeId,
-    subscribers: UnsubscriberArray,
-}
-
-type UnsubscriberArray = Vec<Rc<RefCell<Vec<ScopeId>>>>;
-
-impl Drop for Unsubscriber {
-    fn drop(&mut self) {
-        for subscribers in &self.subscribers {
-            subscribers.borrow_mut().retain(|s| *s != self.scope);
-        }
-    }
-}
-
-fn current_unsubscriber() -> Rc<RefCell<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(Rc::new(RefCell::new(owner)))
-        }
-    }
-}
-
-struct SignalSubscriberDrop<T: 'static, S: Storage<SignalData<T>>> {
-    signal: Signal<T, S>,
-}
-
-impl<T: 'static, S: Storage<SignalData<T>>> Drop for SignalSubscriberDrop<T, S> {
-    fn drop(&mut self) {
-        self.signal.update_subscribers();
-    }
-}
-
 /// A mutable reference to a signal's value.
 ///
 /// T is the current type of the write
@@ -489,3 +370,13 @@ impl<T: ?Sized, S: AnyStorage> DerefMut for Write<T, S> {
         &mut self.write
     }
 }
+
+struct SignalSubscriberDrop<T: 'static, S: Storage<SignalData<T>>> {
+    signal: Signal<T, S>,
+}
+
+impl<T: 'static, S: Storage<SignalData<T>>> Drop for SignalSubscriberDrop<T, S> {
+    fn drop(&mut self) {
+        self.signal.update_subscribers();
+    }
+}