From dd06705ff17e3df738ef17f62a2775d66078bc6d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 1 Feb 2024 01:26:05 -0800 Subject: [PATCH] implement use_async_memo with reactive context --- examples/readme.rs | 14 ++++++- packages/hooks/src/use_effect.rs | 2 +- packages/hooks/src/use_memo.rs | 5 --- packages/hooks/src/use_resource.rs | 65 ++++++++++++++++++------------ packages/signals/src/effect.rs | 2 +- packages/signals/src/lib.rs | 4 +- packages/signals/src/rc.rs | 34 ++++++---------- 7 files changed, 68 insertions(+), 58 deletions(-) diff --git a/examples/readme.rs b/examples/readme.rs index ea0fc869f..034055a99 100644 --- a/examples/readme.rs +++ b/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) -> 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}" } } diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index 8e86ec692..ee9711881 100644 --- a/packages/hooks/src/use_effect.rs +++ b/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. diff --git a/packages/hooks/src/use_memo.rs b/packages/hooks/src/use_memo.rs index 944d5504f..7ca070731 100644 --- a/packages/hooks/src/use_memo.rs +++ b/packages/hooks/src/use_memo.rs @@ -57,13 +57,8 @@ pub fn use_maybe_sync_memo>>( 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; } } diff --git a/packages/hooks/src/use_resource.rs b/packages/hooks/src/use_resource.rs index cfa3799fe..427334e14 100644 --- a/packages/hooks/src/use_resource.rs +++ b/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 + '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); + 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; + // 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 - // value.set(Some(Signal::new(res))); - }); + // Set the value and state + state.set(UseResourceState::Complete); + value.set(Some(Signal::new(res))); + }) + }); - Some(task) + 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 { value: Signal>>, - task: Signal>, - state: Signal>, + task: Signal, + state: Signal, } impl AsyncMemo { @@ -93,7 +106,7 @@ impl AsyncMemo { } /// Get the current state of the future. - pub fn state(&self) -> UseResourceState { + 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 AsyncMemo { } } -pub enum UseResourceState { +pub enum UseResourceState { Pending, - Complete(Signal), - Regenerating(Signal), // the old value + Complete, + Regenerating, // the old value } diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs index d459ca75f..56f427314 100644 --- a/packages/signals/src/effect.rs +++ b/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, diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index 8b49a9afd..3c194f93d 100644 --- a/packages/signals/src/lib.rs +++ b/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::*; diff --git a/packages/signals/src/rc.rs b/packages/signals/src/rc.rs index 999bc42da..70cf87d85 100644 --- a/packages/signals/src/rc.rs +++ b/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, + signal_subscribers: FxHashMap, scope_subscribers: FxHashSet, - _self: Option, + self_: Option, // 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()); }); } }