From 7d78e5e2500f9c34258045dd633954d72b7ee161 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 3 Feb 2024 13:10:16 -0800 Subject: [PATCH] Fix warnings and compile errors --- .../examples/salvo-hello-world/src/main.rs | 2 +- packages/hooks/src/use_resource.rs | 5 + packages/signals/src/effect.rs | 202 ------------------ packages/signals/src/lib.rs | 3 - packages/signals/src/rc.rs | 6 + packages/signals/src/rt.rs | 35 +-- packages/signals/src/signal.rs | 14 +- 7 files changed, 26 insertions(+), 241 deletions(-) delete mode 100644 packages/signals/src/effect.rs diff --git a/packages/fullstack/examples/salvo-hello-world/src/main.rs b/packages/fullstack/examples/salvo-hello-world/src/main.rs index b7cd8c48e..71fae37e8 100644 --- a/packages/fullstack/examples/salvo-hello-world/src/main.rs +++ b/packages/fullstack/examples/salvo-hello-world/src/main.rs @@ -10,7 +10,7 @@ use dioxus_fullstack::{launch, prelude::*}; use serde::{Deserialize, Serialize}; fn app() -> Element { - let state = use_server_future(move || async move { get_server_data().await.unwrap() }); + let state = use_server_future(move || async move { get_server_data().await.unwrap() })?; let mut count = use_signal(|| 0); let mut text = use_signal(|| "...".to_string()); diff --git a/packages/hooks/src/use_resource.rs b/packages/hooks/src/use_resource.rs index c0e871cc2..d7c84f28c 100644 --- a/packages/hooks/src/use_resource.rs +++ b/packages/hooks/src/use_resource.rs @@ -147,6 +147,11 @@ impl Resource { self.state.into() } + /// Get the current value of the future. + pub fn value(&self) -> Option> { + self.value.peek().as_ref().map(|sig| sig.clone().into()) + } + /// Wait for this async memo to resolve, returning the inner signal value /// If the value is pending, returns none and suspends the current component pub fn suspend(&self) -> Option> { diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs deleted file mode 100644 index 56f427314..000000000 --- a/packages/signals/src/effect.rs +++ /dev/null @@ -1,202 +0,0 @@ -use crate::write::*; -use crate::CopyValue; -use core::{self, fmt::Debug}; -use dioxus_core::prelude::*; -use futures_channel::mpsc::UnboundedSender; -use futures_util::{future::Either, pin_mut, StreamExt}; -use generational_box::GenerationalBoxId; -use parking_lot::RwLock; -use rustc_hash::FxHashMap; -use std::fmt::{self, Formatter}; - -thread_local! { - pub(crate)static EFFECT_STACK: EffectStack = EffectStack::default(); -} - -#[derive(Default)] -pub(crate) struct EffectStack { - pub(crate) effects: RwLock>, - pub(crate) effect_mapping: RwLock>, -} - -impl EffectStack { - pub(crate) fn current(&self) -> Option { - self.effects.read().last().copied() - } -} - -/// This is a thread safe reference to an effect stack running on another thread. -#[derive(Clone)] -pub(crate) struct EffectStackRef { - rerun_effect: UnboundedSender, -} - -impl EffectStackRef { - pub(crate) fn rerun_effect(&self, id: GenerationalBoxId) { - self.rerun_effect.unbounded_send(id).unwrap(); - } -} - -pub(crate) fn get_effect_ref() -> EffectStackRef { - if let Some(rt) = try_consume_context() { - return rt; - } - - let (sender, receiver) = futures_channel::mpsc::unbounded(); - - spawn_forever(async move { effect_driver(receiver).await }); - - let stack_ref = EffectStackRef { - rerun_effect: sender, - }; - - provide_root_context(stack_ref.clone()); - - stack_ref -} - -/// The primary top-level driver of all effects -/// -/// In Dioxus, effects are neither react effects nor solidjs effects. They are a hybrid of the two, making our model -/// more complex but also more powerful. -/// -/// In react, when a component renders, it can queue up effects to be run after the component is done rendering. -/// This is done *only during render* and determined by the dependency array attached to the effect. In Dioxus, -/// we track effects using signals, so these effects can actually run multiple times after the component has rendered. -/// -/// -async fn effect_driver( - mut receiver: futures_channel::mpsc::UnboundedReceiver, -) -> ! { - let mut queued_memos = Vec::new(); - - loop { - // Wait for a flush - // This gives a chance for effects to be updated in place and memos to compute their values - let flush_await = flush_sync(); - pin_mut!(flush_await); - - // Until the flush is ready, wait for a new effect to be queued - // We don't run the effects immediately because we want to batch them on the next call to flush - // todo: the queued memos should be unqueued when components are dropped - loop { - match futures_util::future::select(&mut flush_await, receiver.next()).await { - // VDOM is flushed and we can run the queued effects - Either::Left(_flushed) => break, - - // A new effect was queued to be run after the next flush - // Marking components as dirty is handled syncrhonously on write, though we could try - // batching them here too - Either::Right((_queued, _)) => { - if let Some(task) = _queued { - queued_memos.push(task); - } - } - } - } - - EFFECT_STACK.with(|stack| { - for id in queued_memos.drain(..) { - let effect_mapping = stack.effect_mapping.read(); - if let Some(mut effect) = effect_mapping.get(&id).copied() { - tracing::trace!("Rerunning effect: {:?}", id); - effect.try_run(); - } else { - tracing::trace!("Effect not found: {:?}", id); - } - } - }); - } -} - -/// 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) source: ScopeId, - pub(crate) inner: CopyValue, -} - -pub(crate) struct EffectInner { - pub(crate) callback: Box, - pub(crate) id: GenerationalBoxId, -} - -impl EffectInner { - pub(crate) fn new(callback: Box) -> CopyValue { - let mut copy = CopyValue::invalid(); - let inner = EffectInner { - callback: Box::new(callback), - id: copy.id(), - }; - copy.set(inner); - copy - } -} - -impl Drop for EffectInner { - fn drop(&mut self) { - EFFECT_STACK.with(|stack| { - tracing::trace!("Dropping effect: {:?}", self.id); - stack.effect_mapping.write().remove(&self.id); - }); - } -} - -impl Debug for Effect { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_fmt(format_args!("{:?}", self.inner.value)) - } -} - -impl Effect { - pub(crate) fn current() -> Option { - EFFECT_STACK.with(|stack| stack.effects.read().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. - fn new(mut callback: impl FnMut() + 'static) -> Self { - let source = current_scope_id().expect("in a virtual dom"); - let myself = Self { - source, - #[allow(clippy::redundant_closure)] - inner: EffectInner::new(Box::new(move || source.in_runtime(|| callback()))), - }; - - EFFECT_STACK.with(|stack| { - stack - .effect_mapping - .write() - .insert(myself.inner.id(), myself); - }); - tracing::trace!("Created effect: {:?}", myself); - - get_effect_ref().rerun_effect(myself.inner.id()); - - myself - } - - /// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead. - pub fn try_run(&mut self) { - tracing::trace!("Running effect: {:?}", self); - if let Ok(mut inner) = self.inner.try_write() { - { - EFFECT_STACK.with(|stack| { - stack.effects.write().push(*self); - }); - } - (inner.callback)(); - { - EFFECT_STACK.with(|stack| { - stack.effects.write().pop(); - }); - } - } - } - - /// Get the id of this effect. - pub fn id(&self) -> GenerationalBoxId { - self.inner.id() - } -} diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index 3c194f93d..d06308b0a 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -7,9 +7,6 @@ mod rt; pub use rt::*; -mod effect; -pub use effect::*; - pub(crate) mod signal; pub use signal::*; diff --git a/packages/signals/src/rc.rs b/packages/signals/src/rc.rs index 8aeebf852..709b16067 100644 --- a/packages/signals/src/rc.rs +++ b/packages/signals/src/rc.rs @@ -23,6 +23,7 @@ thread_local! { } impl ReactiveContext { + /// Create a new reactive context pub fn new(scope: Option) -> Self { let (tx, rx) = flume::unbounded(); @@ -104,6 +105,11 @@ impl ReactiveContext { .insert(signal, rc_list); } + /// Get the scope that inner CopyValue is associated with + pub fn origin_scope(&self) -> ScopeId { + self.inner.origin_scope() + } + /// Wait for this reactive context to change pub async fn changed(&self) { let rx = self.inner.read().receiver.clone(); diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index 1302b19fb..f5039f102 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -13,9 +13,8 @@ use dioxus_core::ScopeId; use generational_box::{GenerationalBox, Owner, Storage}; -use crate::Effect; -use crate::Readable; use crate::Writable; +use crate::{ReactiveContext, Readable}; /// Run a closure with the given owner. pub fn with_owner R, R>(owner: Owner, f: F) -> R { @@ -85,20 +84,15 @@ fn current_owner, T>() -> Owner { return owner; } - match Effect::current() { - // If we are inside of an effect, we should use the owner of the effect as the owner of the value. - Some(effect) => { - let scope_id = effect.source; - owner_in_scope(scope_id) - } - // Otherwise either get an owner from the current scope or create a new one. - None => match has_context() { - Some(rt) => rt, - None => { - let owner = S::owner(); - provide_context(owner) - } - }, + // If we are inside of an effect, we should use the owner of the effect as the owner of the value. + if let Some(scope) = ReactiveContext::current() { + return owner_in_scope(scope.origin_scope()); + } + + // Otherwise either get an owner from the current scope or create a new one. + match has_context() { + Some(rt) => rt, + None => provide_context(S::owner()), } } @@ -206,15 +200,6 @@ impl> CopyValue { .expect("value is already dropped or borrowed") } - pub(crate) fn invalid() -> Self { - let owner = current_owner(); - - Self { - value: owner.invalid(), - origin_scope: current_scope_id().expect("in a virtual dom"), - } - } - /// Get the scope this value was created in. pub fn origin_scope(&self) -> ScopeId { self.origin_scope diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index e4298f485..9406aa578 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,23 +1,17 @@ use crate::{ - get_effect_ref, read::Readable, write::Writable, CopyValue, Effect, EffectInner, - EffectStackRef, GlobalMemo, GlobalSignal, MappedSignal, ReactiveContext, ReadOnlySignal, - EFFECT_STACK, + read::Readable, write::Writable, CopyValue, GlobalMemo, GlobalSignal, MappedSignal, + ReactiveContext, ReadOnlySignal, }; use dioxus_core::{ - prelude::{ - current_scope_id, flush_sync, has_context, provide_context, schedule_update_any, spawn, - IntoAttributeValue, - }, + prelude::{flush_sync, spawn, IntoAttributeValue}, ScopeId, }; -use generational_box::{AnyStorage, GenerationalBoxId, Storage, SyncStorage, UnsyncStorage}; +use generational_box::{AnyStorage, Storage, SyncStorage, UnsyncStorage}; use parking_lot::RwLock; use std::{ any::Any, - cell::RefCell, collections::HashSet, ops::{Deref, DerefMut}, - rc::Rc, sync::Arc, };