From a6f611eccf2332c40be22ba052a8d30bab6b13c8 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 4 Aug 2023 17:23:57 -0700 Subject: [PATCH] fix subscriptions in events --- examples/clock.rs | 62 +++++++-- packages/copy/Cargo.toml | 2 +- packages/copy/src/lib.rs | 11 ++ packages/core/src/arena.rs | 2 +- packages/core/src/events.rs | 6 +- packages/core/src/nodes.rs | 6 +- packages/core/src/scopes.rs | 26 ++-- packages/core/src/virtual_dom.rs | 10 +- packages/signals/src/effect.rs | 52 ++++++++ packages/signals/src/impls.rs | 212 +++++++++++++++++++++++++++++++ packages/signals/src/lib.rs | 125 ++++++++++-------- packages/signals/src/rt.rs | 34 +++-- 12 files changed, 445 insertions(+), 103 deletions(-) create mode 100644 packages/signals/src/effect.rs create mode 100644 packages/signals/src/impls.rs diff --git a/examples/clock.rs b/examples/clock.rs index e59538ed..b0ea29b4 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -1,26 +1,60 @@ -//! Example: README.md showcase -//! -//! The example from the README.md. +#![allow(non_snake_case)] use dioxus::prelude::*; -use dioxus_signals::use_signal; +use dioxus_signals::{use_signal, Effect, Signal}; fn main() { dioxus_desktop::launch(app); } fn app(cx: Scope) -> Element { - let mut count = use_signal(cx, || 0); + let counts = use_signal(cx, || (0..100).map(Signal::new).collect::>()); - use_future!(cx, || async move { - loop { - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - count += 1; - println!("current: {count}"); - } + cx.use_hook(|| { + Effect::new(move || { + println!("Counts: {:?}", counts); + }) }); - cx.render(rsx! { - div { "High-Five counter: {count}" } - }) + render! { + for count in counts { + Child { + count: count, + } + } + } +} + +#[derive(Props, PartialEq)] +struct ChildProps { + count: Signal, +} + +fn Child(cx: Scope) -> Element { + let count = cx.props.count; + + // use_future!(cx, || async move { + // loop { + // tokio::time::sleep(std::time::Duration::from_secs(count.value())).await; + // *count.write() += 1; + // } + // }); + + render! { + div { + "Child: {count}" + button { + onclick: move |_| { + *count.write() += 1; + }, + "Increase" + } + button { + onclick: move |_| { + *count.write() -= 1; + }, + "Decrease" + } + } + } } diff --git a/packages/copy/Cargo.toml b/packages/copy/Cargo.toml index 9c5e8406..56964bad 100644 --- a/packages/copy/Cargo.toml +++ b/packages/copy/Cargo.toml @@ -14,4 +14,4 @@ rand = "0.8.5" [features] default = ["check_generation"] -check_generation = [] \ No newline at end of file +check_generation = [] diff --git a/packages/copy/src/lib.rs b/packages/copy/src/lib.rs index 8e8b8053..3b091d30 100644 --- a/packages/copy/src/lib.rs +++ b/packages/copy/src/lib.rs @@ -193,6 +193,17 @@ impl CopyHandle { pub fn write(&self) -> RefMut<'_, T> { self.try_write().unwrap() } + + pub fn ptr_eq(&self, other: &Self) -> bool { + #[cfg(any(debug_assertions, feature = "check_generation"))] + { + self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation + } + #[cfg(not(any(debug_assertions, feature = "check_generation")))] + { + self.raw.data.as_ptr() == other.raw.data.as_ptr() + } + } } impl Copy for CopyHandle {} diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 30a9cba8..b7ec4662 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -163,7 +163,7 @@ impl VirtualDom { let listener = unsafe { &*listener }; match &listener.value { AttributeValue::Listener(l) => { - _ = l.take(); + _ = l.callback.take(); } AttributeValue::Any(a) => { _ = a.take(); diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index ce958db4..c734ebd4 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -1,7 +1,9 @@ use std::{ cell::{Cell, RefCell}, rc::Rc, + }; +use crate::ScopeId; /// A wrapper around some generic data that handles the event's state /// @@ -134,13 +136,15 @@ impl std::fmt::Debug for Event { /// } /// /// ``` -pub struct EventHandler<'bump, T = ()> { +pub struct EventHandler<'bump, T:?Sized = ()> { + pub(crate) origin: ScopeId, pub(super) callback: RefCell>>, } impl Default for EventHandler<'_, T> { fn default() -> Self { Self { + origin: ScopeId(0), callback: Default::default(), } } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index a108f357..5406e8f0 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,4 +1,4 @@ -use crate::{ +use crate::prelude::EventHandler;use crate::{ any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState, }; use bumpalo::boxed::Box as BumpBox; @@ -476,7 +476,7 @@ pub enum AttributeValue<'a> { Bool(bool), /// A listener, like "onclick" - Listener(RefCell>>), + Listener(EventHandler<'a, Event>), /// An arbitrary value that implements PartialEq and is static Any(RefCell>>), @@ -485,8 +485,6 @@ pub enum AttributeValue<'a> { None, } -pub type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event) + 'a>; - /// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements that are borrowed /// /// These varients are used to communicate what the value of an attribute is that needs to be updated diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 246d2801..b43b2143 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -446,7 +446,7 @@ impl<'src> ScopeState { let handler: &mut dyn FnMut(T) = self.bump().alloc(f); let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) }; let callback = RefCell::new(Some(caller)); - EventHandler { callback } + EventHandler { callback, origin: self.context().id } } /// Create a new [`AttributeValue`] with the listener variant from a callback @@ -456,22 +456,16 @@ impl<'src> ScopeState { &'src self, mut callback: impl FnMut(Event) + 'src, ) -> AttributeValue<'src> { - // safety: there's no other way to create a dynamicly-dispatched bump box other than alloc + from-raw - // This is the suggested way to build a bumpbox - // - // In theory, we could just use regular boxes - let boxed: BumpBox<'src, dyn FnMut(_) + 'src> = unsafe { - BumpBox::from_raw(self.bump().alloc(move |event: Event| { - if let Ok(data) = event.data.downcast::() { - callback(Event { - propagates: event.propagates, - data, - }); - } - })) - }; + - AttributeValue::Listener(RefCell::new(Some(boxed))) + AttributeValue::Listener(self.event_handler(move |event: Event| { + if let Ok(data) = event.data.downcast::() { + callback(Event { + propagates: event.propagates, + data, + }); + } + })) } /// Create a new [`AttributeValue`] with a value that implements [`AnyValue`] diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 86094da9..c43af225 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -391,9 +391,12 @@ impl VirtualDom { // We check the bubble state between each call to see if the event has been stopped from bubbling for listener in listeners.drain(..).rev() { if let AttributeValue::Listener(listener) = listener { - if let Some(cb) = listener.borrow_mut().as_deref_mut() { + let origin = listener.origin; + self.runtime.scope_stack.borrow_mut().push(origin); + if let Some(cb) = listener.callback.borrow_mut().as_deref_mut() { cb(uievent.clone()); } + self.runtime.scope_stack.borrow_mut().pop(); if !uievent.propagates.get() { return; @@ -422,9 +425,12 @@ impl VirtualDom { // Only call the listener if this is the exact target element. if attr.name.trim_start_matches("on") == name && target_path == this_path { if let AttributeValue::Listener(listener) = &attr.value { - if let Some(cb) = listener.borrow_mut().as_deref_mut() { + let origin = listener.origin; + self.runtime.scope_stack.borrow_mut().push(origin); + if let Some(cb) = listener.callback.borrow_mut().as_deref_mut() { cb(uievent.clone()); } + self.runtime.scope_stack.borrow_mut().pop(); break; } diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs new file mode 100644 index 00000000..9bc9b33f --- /dev/null +++ b/packages/signals/src/effect.rs @@ -0,0 +1,52 @@ +use dioxus_core::prelude::*; + +use crate::CopyValue; + +#[derive(Default, Clone, Copy)] +pub(crate) struct EffectStack { + pub(crate) effects: CopyValue>, +} + +pub(crate) fn get_effect_stack() -> EffectStack { + match consume_context() { + Some(rt) => rt, + None => { + let store = EffectStack::default(); + provide_root_context(store).expect("in a virtual dom") + } + } +} + +#[derive(Copy, Clone, PartialEq)] +pub struct Effect { + callback: CopyValue>, +} + +impl Effect { + pub(crate) fn current() -> Option { + get_effect_stack().effects.read().last().copied() + } + + pub fn new(callback: impl FnMut() + 'static) -> Self { + let myself = Self { + callback: CopyValue::new(Box::new(callback)), + }; + + myself.try_run(); + + myself + } + + /// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead. + pub fn try_run(&self) { + if let Some(mut callback) = self.callback.try_write() { + { + get_effect_stack().effects.write().push(*self); + } + callback(); + { + get_effect_stack().effects.write().pop(); + } + } + } +} diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs new file mode 100644 index 00000000..d54e5251 --- /dev/null +++ b/packages/signals/src/impls.rs @@ -0,0 +1,212 @@ +use crate::rt::CopyValue; +use crate::Signal; + +use std::cell::{Ref, RefMut}; + +use std::{ + fmt::{Debug, Display}, + ops::{Add, Div, Mul, Sub}, +}; + +macro_rules! impls { + ($ty:ident) => { + impl Default for $ty { + fn default() -> Self { + Self::new(T::default()) + } + } + + impl std::clone::Clone for $ty { + fn clone(&self) -> Self { + *self + } + } + + impl Copy for $ty {} + + impl Display for $ty { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.with(|v| Display::fmt(v, f)) + } + } + + impl Debug for $ty { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.with(|v| Debug::fmt(v, f)) + } + } + + impl + Copy + 'static> std::ops::AddAssign for $ty { + fn add_assign(&mut self, rhs: T) { + self.set(self.value() + rhs); + } + } + + impl + Copy + 'static> std::ops::SubAssign for $ty { + fn sub_assign(&mut self, rhs: T) { + self.set(self.value() - rhs); + } + } + + impl + Copy + 'static> std::ops::MulAssign for $ty { + fn mul_assign(&mut self, rhs: T) { + self.set(self.value() * rhs); + } + } + + impl + Copy + 'static> std::ops::DivAssign for $ty { + fn div_assign(&mut self, rhs: T) { + self.set(self.value() / rhs); + } + } + + impl $ty> { + pub fn push(&self, value: T) { + self.with_mut(|v| v.push(value)) + } + + pub fn pop(&self) -> Option { + self.with_mut(|v| v.pop()) + } + + pub fn insert(&self, index: usize, value: T) { + self.with_mut(|v| v.insert(index, value)) + } + + pub fn remove(&self, index: usize) -> T { + self.with_mut(|v| v.remove(index)) + } + + pub fn clear(&self) { + self.with_mut(|v| v.clear()) + } + + pub fn extend(&self, iter: impl IntoIterator) { + self.with_mut(|v| v.extend(iter)) + } + + pub fn truncate(&self, len: usize) { + self.with_mut(|v| v.truncate(len)) + } + + pub fn swap_remove(&self, index: usize) -> T { + self.with_mut(|v| v.swap_remove(index)) + } + + pub fn retain(&self, f: impl FnMut(&T) -> bool) { + self.with_mut(|v| v.retain(f)) + } + + pub fn split_off(&self, at: usize) -> Vec { + self.with_mut(|v| v.split_off(at)) + } + + pub fn get(&self, index: usize) -> Option> { + Ref::filter_map(self.read(), |v| v.get(index)).ok() + } + + pub fn get_mut(&self, index: usize) -> Option> { + RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok() + } + } + + impl $ty> { + pub fn take(&self) -> Option { + self.with_mut(|v| v.take()) + } + + pub fn replace(&self, value: T) -> Option { + self.with_mut(|v| v.replace(value)) + } + + pub fn unwrap(&self) -> T + where + T: Clone, + { + self.with(|v| v.clone()).unwrap() + } + + pub fn as_ref(&self) -> Option> { + Ref::filter_map(self.read(), |v| v.as_ref()).ok() + } + + pub fn as_mut(&self) -> Option> { + RefMut::filter_map(self.write(), |v| v.as_mut()).ok() + } + + pub fn get_or_insert(&self, default: T) -> Ref<'_, T> { + self.get_or_insert_with(|| default) + } + + pub fn get_or_insert_with(&self, default: impl FnOnce() -> T) -> Ref<'_, T> { + let borrow = self.read(); + if borrow.is_none() { + drop(borrow); + self.with_mut(|v| *v = Some(default())); + Ref::map(self.read(), |v| v.as_ref().unwrap()) + } else { + Ref::map(borrow, |v| v.as_ref().unwrap()) + } + } + } + }; +} + +impls!(CopyValue); +impls!(Signal); + +pub struct CopyValueIterator { + index: usize, + value: CopyValue>, +} + +impl Iterator for CopyValueIterator { + type Item = T; + + fn next(&mut self) -> Option { + let index = self.index; + self.index += 1; + self.value.get(index).map(|v| v.clone()) + } +} + +impl IntoIterator for CopyValue> { + type IntoIter = CopyValueIterator; + + type Item = T; + + fn into_iter(self) -> Self::IntoIter { + CopyValueIterator { + index: 0, + value: self, + } + } +} + +pub struct CopySignalIterator { + index: usize, + value: Signal>, +} + +impl Iterator for CopySignalIterator { + type Item = T; + + fn next(&mut self) -> Option { + let index = self.index; + self.index += 1; + self.value.get(index).map(|v| v.clone()) + } +} + +impl IntoIterator for Signal> { + type IntoIter = CopySignalIterator; + + type Item = T; + + fn into_iter(self) -> Self::IntoIter { + CopySignalIterator { + index: 0, + value: self, + } + } +} diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index cfb25c96..4340b648 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -1,15 +1,18 @@ use std::{ - cell::{Ref, RefMut}, - fmt::{Debug, Display}, - ops::{Add, Div, Mul, Sub}, + cell::{Ref, RefCell, RefMut}, + rc::Rc, sync::Arc, }; mod rt; pub use rt::*; +mod effect; +pub use effect::*; +#[macro_use] +mod impls; use dioxus_core::{ - prelude::{current_scope_id, schedule_update_any}, + prelude::{current_scope_id, has_context, provide_context, schedule_update_any}, ScopeId, ScopeState, }; @@ -17,8 +20,36 @@ pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal< *cx.use_hook(|| Signal::new(f())) } +#[derive(Clone)] +struct Unsubscriber { + scope: ScopeId, + subscribers: Rc>>>>>, +} + +impl Drop for Unsubscriber { + fn drop(&mut self) { + for subscribers in self.subscribers.borrow().iter() { + subscribers.borrow_mut().retain(|s| *s != self.scope); + } + } +} + +fn current_unsubscriber() -> 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(owner).expect("in a virtual dom") + } + } +} + struct SignalData { - subscribers: Vec, + subscribers: Rc>>, + effect_subscribers: Rc>>, update_any: Arc, value: T, } @@ -31,7 +62,8 @@ impl Signal { pub fn new(value: T) -> Self { Self { inner: CopyValue::new(SignalData { - subscribers: Vec::new(), + subscribers: Default::default(), + effect_subscribers: Default::default(), update_any: schedule_update_any().expect("in a virtual dom"), value, }), @@ -39,21 +71,40 @@ impl Signal { } pub fn read(&self) -> Ref { + let inner = self.inner.read(); if let Some(current_scope_id) = current_scope_id() { - let mut inner = self.inner.write(); - if !inner.subscribers.contains(¤t_scope_id) { - inner.subscribers.push(current_scope_id); + let mut subscribers = inner.subscribers.borrow_mut(); + if !subscribers.contains(¤t_scope_id) { + subscribers.push(current_scope_id); + drop(subscribers); + let unsubscriber = current_unsubscriber(); + inner.subscribers.borrow_mut().push(unsubscriber.scope); } } - Ref::map(self.inner.read(), |v| &v.value) + if let Some(effect) = Effect::current() { + let mut effect_subscribers = inner.effect_subscribers.borrow_mut(); + if !effect_subscribers.contains(&effect) { + effect_subscribers.push(effect); + } + } + Ref::map(inner, |v| &v.value) } pub fn write(&self) -> RefMut { - let inner = self.inner.write(); - for &scope_id in &inner.subscribers { - (inner.update_any)(scope_id); + { + let inner = self.inner.read(); + for &scope_id in &*inner.subscribers.borrow() { + (inner.update_any)(scope_id); + } } + let subscribers = + { std::mem::take(&mut *self.inner.read().effect_subscribers.borrow_mut()) }; + for effect in subscribers { + effect.try_run(); + } + + let inner = self.inner.write(); RefMut::map(inner, |v| &mut v.value) } @@ -66,58 +117,20 @@ impl Signal { f(&*write) } - pub fn update(&self, f: impl FnOnce(&mut T) -> O) -> O { + pub fn with_mut(&self, f: impl FnOnce(&mut T) -> O) -> O { let mut write = self.write(); f(&mut *write) } } impl Signal { - pub fn get(&self) -> T { + pub fn value(&self) -> T { self.read().clone() } } -impl std::clone::Clone for Signal { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Signal {} - -impl Display for Signal { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.with(|v| Display::fmt(v, f)) - } -} - -impl Debug for Signal { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.with(|v| Debug::fmt(v, f)) - } -} - -impl + Copy + 'static> std::ops::AddAssign for Signal { - fn add_assign(&mut self, rhs: T) { - self.set(self.get() + rhs); - } -} - -impl + Copy + 'static> std::ops::SubAssign for Signal { - fn sub_assign(&mut self, rhs: T) { - self.set(self.get() - rhs); - } -} - -impl + Copy + 'static> std::ops::MulAssign for Signal { - fn mul_assign(&mut self, rhs: T) { - self.set(self.get() * rhs); - } -} - -impl + Copy + 'static> std::ops::DivAssign for Signal { - fn div_assign(&mut self, rhs: T) { - self.set(self.get() / rhs); +impl PartialEq for Signal { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner } } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index a47b5764..0605fb95 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -26,14 +26,6 @@ fn current_owner() -> Rc { } } -impl Copy for CopyValue {} - -impl Clone for CopyValue { - fn clone(&self) -> Self { - *self - } -} - pub struct CopyValue { pub value: CopyHandle, } @@ -62,4 +54,30 @@ impl CopyValue { pub fn write(&self) -> RefMut<'_, T> { self.value.write() } + + pub fn set(&mut self, value: T) { + *self.write() = value; + } + + pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { + let write = self.read(); + f(&*write) + } + + pub fn with_mut(&self, f: impl FnOnce(&mut T) -> O) -> O { + let mut write = self.write(); + f(&mut *write) + } +} + +impl CopyValue { + pub fn value(&self) -> T { + self.read().clone() + } +} + +impl PartialEq for CopyValue { + fn eq(&self, other: &Self) -> bool { + self.value.ptr_eq(&other.value) + } }