Преглед на файлове

implement use_async_memo with reactive context

Jonathan Kelley преди 1 година
родител
ревизия
dd06705ff1

+ 13 - 1
examples/readme.rs

@@ -1,3 +1,5 @@
+use std::time::Duration;
+
 use dioxus::prelude::*;
 
 fn main() {
@@ -23,7 +25,17 @@ fn app() -> Element {
 fn Child(sig: Signal<i32>) -> Element {
     let doubled = use_memo(move || sig() * 2);
 
+    let tripled = use_async_memo(move || async move {
+        tokio::time::sleep(Duration::from_millis(200)).await;
+        sig() * 3
+    });
+
+    let trippled = use_memo(move || match tripled.value() {
+        Some(v) => v.cloned(),
+        None => 1338,
+    });
+
     rsx! {
-        "The count is: {sig}, doubled: {doubled}"
+        "The count is: {sig}, doubled: {doubled}, tripled: {trippled}"
     }
 }

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

@@ -1,7 +1,7 @@
 use crate::use_hook_did_run;
 use dioxus_core::prelude::*;
 use dioxus_signals::{CopyValue, Effect, ReactiveContext, Writable};
-use futures_util::select;
+// 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.

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

@@ -57,13 +57,8 @@ pub fn use_maybe_sync_memo<R: PartialEq, S: Storage<SignalData<R>>>(
         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;
                 }
             }

+ 42 - 29
packages/hooks/src/use_resource.rs

@@ -1,8 +1,8 @@
 #![allow(missing_docs)]
 
-use crate::use_signal;
+use crate::{use_callback, use_signal};
 use dioxus_core::{
-    prelude::{spawn, suspend},
+    prelude::{spawn, suspend, use_hook},
     Task,
 };
 use dioxus_signals::*;
@@ -19,31 +19,44 @@ where
     F: Future<Output = T> + 'static,
 {
     let mut value = use_signal(|| None);
-    let state = use_signal(|| UseResourceState::Pending);
+    let mut state = use_signal(|| UseResourceState::Pending);
+    let rc = use_hook(|| ReactiveContext::new(None));
 
-    let task = use_signal(|| {
+    let mut cb = use_callback(move || {
         // Create the user's task
-        let fut = future();
+        let fut = rc.run_in(|| future());
 
         // 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)));
-        });
-
-        Some(task)
+        spawn(async move {
+            // move the future here and pin it so we can poll it
+            let fut = fut;
+            pin_mut!(fut);
+
+            // Run each poll in the context of the reactive scope
+            // This ensures the scope is properly subscribed to the future's dependencies
+            let res = future::poll_fn(|cx| rc.run_in(|| fut.poll_unpin(cx))).await;
+
+            // Set the value and state
+            state.set(UseResourceState::Complete);
+            value.set(Some(Signal::new(res)));
+        })
+    });
+
+    let mut task = use_hook(|| Signal::new(cb.call()));
+
+    use_hook(|| {
+        spawn(async move {
+            loop {
+                // Wait for the dependencies to change
+                rc.changed().await;
+
+                // Stop the old task
+                task.write().cancel();
+
+                // Start a new task
+                task.set(cb.call());
+            }
+        })
     });
 
     AsyncMemo { task, value, state }
@@ -52,8 +65,8 @@ where
 #[allow(unused)]
 pub struct AsyncMemo<T: 'static> {
     value: Signal<Option<Signal<T>>>,
-    task: Signal<Option<Task>>,
-    state: Signal<UseResourceState<T>>,
+    task: Signal<Task>,
+    state: Signal<UseResourceState>,
 }
 
 impl<T> AsyncMemo<T> {
@@ -93,7 +106,7 @@ impl<T> AsyncMemo<T> {
     }
 
     /// Get the current state of the future.
-    pub fn state(&self) -> UseResourceState<T> {
+    pub fn state(&self) -> UseResourceState {
         todo!()
         // match (&self.task.get(), &self.value()) {
         //     // If we have a task and an existing value, we're reloading
@@ -121,8 +134,8 @@ impl<T> AsyncMemo<T> {
     }
 }
 
-pub enum UseResourceState<T: 'static> {
+pub enum UseResourceState {
     Pending,
-    Complete(Signal<T>),
-    Regenerating(Signal<T>), // the old value
+    Complete,
+    Regenerating, // the old value
 }

+ 1 - 1
packages/signals/src/effect.rs

@@ -156,7 +156,7 @@ impl Effect {
     /// 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(mut callback: impl FnMut() + 'static) -> Self {
+    fn new(mut callback: impl FnMut() + 'static) -> Self {
         let source = current_scope_id().expect("in a virtual dom");
         let myself = Self {
             source,

+ 2 - 2
packages/signals/src/lib.rs

@@ -19,8 +19,8 @@ pub use read_only_signal::*;
 mod map;
 pub use map::*;
 
-mod comparer;
-pub use comparer::*;
+// mod comparer;
+// pub use comparer::*;
 
 mod global;
 pub use global::*;

+ 12 - 22
packages/signals/src/rc.rs

@@ -5,7 +5,6 @@ use dioxus_core::prelude::{
 use generational_box::{GenerationalBoxId, SyncStorage};
 use rustc_hash::{FxHashMap, FxHashSet};
 use std::{cell::RefCell, hash::Hash};
-use tokio::signal;
 
 use crate::{CopyValue, RcList, Readable, Writable};
 
@@ -37,17 +36,17 @@ impl ReactiveContext {
             signal_subscribers: FxHashMap::default(),
             scope_subscribers,
             sender: tx,
-            _self: None,
+            self_: None,
             receiver: rx,
         };
 
-        let mut _self = Self {
+        let mut self_ = Self {
             inner: CopyValue::new_maybe_sync(inner),
         };
 
-        _self.inner.write()._self = Some(_self);
+        self_.inner.write().self_ = Some(self_);
 
-        _self
+        self_
     }
 
     /// Get the current reactive context
@@ -60,24 +59,16 @@ impl ReactiveContext {
 
         // 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))))
+        Some(provide_context(ReactiveContext::new(current_scope_id())))
     }
 
     /// Run this function in the context of this reactive context
@@ -96,7 +87,6 @@ impl ReactiveContext {
     /// If there's a scope associated with this context, then it will be marked as dirty too
     pub fn mark_dirty(&self) {
         for scope in self.inner.read().scope_subscribers.iter() {
-            println!("marking dirty {scope:?}");
             needs_update_any(*scope);
         }
 
@@ -105,8 +95,8 @@ impl ReactiveContext {
         _ = 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) {
+    /// Create a two-way binding between this reactive context and a signal
+    pub fn link(&mut self, signal: GenerationalBoxId, rc_list: RcList) {
         rc_list.write().insert(*self);
         self.inner
             .write()
@@ -132,11 +122,11 @@ impl Hash for ReactiveContext {
     }
 }
 
-pub struct Inner {
+struct Inner {
     // Set of signals bound to this context
-    pub signal_subscribers: FxHashMap<GenerationalBoxId, RcList>,
+    signal_subscribers: FxHashMap<GenerationalBoxId, RcList>,
     scope_subscribers: FxHashSet<ScopeId>,
-    _self: Option<ReactiveContext>,
+    self_: Option<ReactiveContext>,
 
     // Futures will call .changed().await
     sender: flume::Sender<()>,
@@ -144,10 +134,10 @@ pub struct Inner {
 }
 
 impl Drop for Inner {
+    // Remove this context from all the subscribers
     fn drop(&mut self) {
-        // Remove this context from all the subscribers
         self.signal_subscribers.values().for_each(|sub_list| {
-            sub_list.write().remove(&self._self.unwrap());
+            sub_list.write().remove(&self.self_.unwrap());
         });
     }
 }