diff --git a/packages/copy/src/lib.rs b/packages/copy/src/lib.rs index 149c14434..b9b8aec06 100644 --- a/packages/copy/src/lib.rs +++ b/packages/copy/src/lib.rs @@ -209,6 +209,12 @@ impl CopyHandle { self.try_write().unwrap() } + pub fn set(&self, value: T) { + self.validate().then(|| { + *self.raw.data.borrow_mut() = Some(Box::new(value)); + }); + } + pub fn ptr_eq(&self, other: &Self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] { @@ -316,6 +322,17 @@ impl Owner { 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(&self) -> CopyHandle { + let location = self.store.claim(); + CopyHandle { + raw: location, + #[cfg(any(debug_assertions, feature = "check_generation"))] + generation: location.generation.get(), + _marker: PhantomData, + } + } } impl Drop for Owner { diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs index 9bc9b33f2..06b02ffd4 100644 --- a/packages/signals/src/effect.rs +++ b/packages/signals/src/effect.rs @@ -1,3 +1,6 @@ +use core::{self, fmt::Debug}; +use std::fmt::{self, Formatter}; + use dioxus_core::prelude::*; use crate::CopyValue; @@ -19,7 +22,13 @@ pub(crate) fn get_effect_stack() -> EffectStack { #[derive(Copy, Clone, PartialEq)] pub struct Effect { - callback: CopyValue>, + pub(crate) callback: CopyValue>, +} + +impl Debug for Effect { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("{:?}", self.callback.value)) + } } impl Effect { diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index c0d081f7d..8319b4b8e 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -8,8 +8,9 @@ mod rt; pub use rt::*; mod effect; pub use effect::*; -#[macro_use] mod impls; +mod memo; +pub use memo::*; use dioxus_core::{ prelude::{current_scope_id, has_context, provide_context, schedule_update_any}, @@ -55,7 +56,7 @@ struct SignalData { } pub struct Signal { - inner: CopyValue>, + pub(crate) inner: CopyValue>, } impl Signal { @@ -115,6 +116,11 @@ impl Signal { let subscribers = { std::mem::take(&mut *self.inner.read().effect_subscribers.borrow_mut()) }; for effect in subscribers { + log::trace!( + "Write on {:?} triggered effect {:?}", + self.inner.value, + effect + ); effect.try_run(); } @@ -122,7 +128,7 @@ impl Signal { RefMut::map(inner, |v| &mut v.value) } - pub fn set(&mut self, value: T) { + pub fn set(&self, value: T) { *self.write() = value; } diff --git a/packages/signals/src/memo.rs b/packages/signals/src/memo.rs new file mode 100644 index 000000000..362f9cbd3 --- /dev/null +++ b/packages/signals/src/memo.rs @@ -0,0 +1,42 @@ +use dioxus_core::prelude::*; + +use crate::{get_effect_stack, CopyValue, Effect, Signal, SignalData}; + +pub fn use_memo(cx: &ScopeState, f: impl FnMut() -> R + 'static) -> Signal { + *cx.use_hook(|| memo(f)) +} + +pub fn memo(mut f: impl FnMut() -> R + 'static) -> Signal { + let state = Signal:: { + inner: CopyValue::invalid(), + }; + let effect = Effect { + callback: CopyValue::invalid(), + }; + + { + get_effect_stack().effects.write().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.write().pop(); + } + + effect.callback.value.set(Box::new(move || { + let value = f(); + let changed = { + let state = state.read(); + value != *state + }; + if changed { + state.set(value) + } + })); + + state +} diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index b37a46a53..db394af5e 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -64,6 +64,15 @@ impl CopyValue { } } + 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 origin_scope(&self) -> ScopeId { self.origin_scope } diff --git a/packages/signals/tests/memo.rs b/packages/signals/tests/memo.rs new file mode 100644 index 000000000..b0f91ee73 --- /dev/null +++ b/packages/signals/tests/memo.rs @@ -0,0 +1,50 @@ +#![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 memos_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); + let memo = cx.use_hook(move || { + to_owned![counter]; + memo(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); +}