소스 검색

fix subscriptions in events

Evan Almloff 1 년 전
부모
커밋
a6f611eccf

+ 48 - 14
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::<Vec<_>>());
 
-    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<u64>,
+}
+
+fn Child(cx: Scope<ChildProps>) -> 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"
+            }
+        }
+    }
 }

+ 1 - 1
packages/copy/Cargo.toml

@@ -14,4 +14,4 @@ rand = "0.8.5"
 
 [features]
 default = ["check_generation"]
-check_generation = []
+check_generation = []

+ 11 - 0
packages/copy/src/lib.rs

@@ -193,6 +193,17 @@ impl<T: 'static> CopyHandle<T> {
     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<T> Copy for CopyHandle<T> {}

+ 1 - 1
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();

+ 5 - 1
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<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
 /// }
 ///
 /// ```
-pub struct EventHandler<'bump, T = ()> {
+pub struct EventHandler<'bump, T:?Sized = ()> {
+    pub(crate) origin: ScopeId,
     pub(super) callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
 }
 
 impl<T> Default for EventHandler<'_, T> {
     fn default() -> Self {
         Self {
+            origin: ScopeId(0),
             callback: Default::default(),
         }
     }

+ 2 - 4
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<Option<ListenerCb<'a>>>),
+    Listener(EventHandler<'a, Event<dyn Any>>),
 
     /// An arbitrary value that implements PartialEq and is static
     Any(RefCell<Option<BumpBox<'a, dyn AnyValue>>>),
@@ -485,8 +485,6 @@ pub enum AttributeValue<'a> {
     None,
 }
 
-pub type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + '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

+ 11 - 17
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<T>) + '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<dyn Any>| {
-                if let Ok(data) = event.data.downcast::<T>() {
-                    callback(Event {
-                        propagates: event.propagates,
-                        data,
-                    });
-                }
-            }))
-        };
-
-        AttributeValue::Listener(RefCell::new(Some(boxed)))
+       
+
+        AttributeValue::Listener(self.event_handler(move |event: Event<dyn Any>| {
+            if let Ok(data) = event.data.downcast::<T>() {
+                callback(Event {
+                    propagates: event.propagates,
+                    data,
+                });
+            }
+        }))
     }
 
     /// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]

+ 8 - 2
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;
                             }

+ 52 - 0
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<Vec<Effect>>,
+}
+
+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<Box<dyn FnMut()>>,
+}
+
+impl Effect {
+    pub(crate) fn current() -> Option<Self> {
+        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();
+            }
+        }
+    }
+}

+ 212 - 0
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<T: Default + 'static> Default for $ty<T> {
+            fn default() -> Self {
+                Self::new(T::default())
+            }
+        }
+
+        impl<T> std::clone::Clone for $ty<T> {
+            fn clone(&self) -> Self {
+                *self
+            }
+        }
+
+        impl<T> Copy for $ty<T> {}
+
+        impl<T: Display + 'static> Display for $ty<T> {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                self.with(|v| Display::fmt(v, f))
+            }
+        }
+
+        impl<T: Debug + 'static> Debug for $ty<T> {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                self.with(|v| Debug::fmt(v, f))
+            }
+        }
+
+        impl<T: Add<Output = T> + Copy + 'static> std::ops::AddAssign<T> for $ty<T> {
+            fn add_assign(&mut self, rhs: T) {
+                self.set(self.value() + rhs);
+            }
+        }
+
+        impl<T: Sub<Output = T> + Copy + 'static> std::ops::SubAssign<T> for $ty<T> {
+            fn sub_assign(&mut self, rhs: T) {
+                self.set(self.value() - rhs);
+            }
+        }
+
+        impl<T: Mul<Output = T> + Copy + 'static> std::ops::MulAssign<T> for $ty<T> {
+            fn mul_assign(&mut self, rhs: T) {
+                self.set(self.value() * rhs);
+            }
+        }
+
+        impl<T: Div<Output = T> + Copy + 'static> std::ops::DivAssign<T> for $ty<T> {
+            fn div_assign(&mut self, rhs: T) {
+                self.set(self.value() / rhs);
+            }
+        }
+
+        impl<T: 'static> $ty<Vec<T>> {
+            pub fn push(&self, value: T) {
+                self.with_mut(|v| v.push(value))
+            }
+
+            pub fn pop(&self) -> Option<T> {
+                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<Item = T>) {
+                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<T> {
+                self.with_mut(|v| v.split_off(at))
+            }
+
+            pub fn get(&self, index: usize) -> Option<Ref<'_, T>> {
+                Ref::filter_map(self.read(), |v| v.get(index)).ok()
+            }
+
+            pub fn get_mut(&self, index: usize) -> Option<RefMut<'_, T>> {
+                RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok()
+            }
+        }
+
+        impl<T: 'static> $ty<Option<T>> {
+            pub fn take(&self) -> Option<T> {
+                self.with_mut(|v| v.take())
+            }
+
+            pub fn replace(&self, value: T) -> Option<T> {
+                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<'_, T>> {
+                Ref::filter_map(self.read(), |v| v.as_ref()).ok()
+            }
+
+            pub fn as_mut(&self) -> Option<RefMut<'_, T>> {
+                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<T: 'static> {
+    index: usize,
+    value: CopyValue<Vec<T>>,
+}
+
+impl<T: Clone> Iterator for CopyValueIterator<T> {
+    type Item = T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let index = self.index;
+        self.index += 1;
+        self.value.get(index).map(|v| v.clone())
+    }
+}
+
+impl<T: Clone + 'static> IntoIterator for CopyValue<Vec<T>> {
+    type IntoIter = CopyValueIterator<T>;
+
+    type Item = T;
+
+    fn into_iter(self) -> Self::IntoIter {
+        CopyValueIterator {
+            index: 0,
+            value: self,
+        }
+    }
+}
+
+pub struct CopySignalIterator<T: 'static> {
+    index: usize,
+    value: Signal<Vec<T>>,
+}
+
+impl<T: Clone> Iterator for CopySignalIterator<T> {
+    type Item = T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let index = self.index;
+        self.index += 1;
+        self.value.get(index).map(|v| v.clone())
+    }
+}
+
+impl<T: Clone + 'static> IntoIterator for Signal<Vec<T>> {
+    type IntoIter = CopySignalIterator<T>;
+
+    type Item = T;
+
+    fn into_iter(self) -> Self::IntoIter {
+        CopySignalIterator {
+            index: 0,
+            value: self,
+        }
+    }
+}

+ 69 - 56
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<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<
     *cx.use_hook(|| Signal::new(f()))
 }
 
+#[derive(Clone)]
+struct Unsubscriber {
+    scope: ScopeId,
+    subscribers: Rc<RefCell<Vec<Rc<RefCell<Vec<ScopeId>>>>>>,
+}
+
+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<T> {
-    subscribers: Vec<ScopeId>,
+    subscribers: Rc<RefCell<Vec<ScopeId>>>,
+    effect_subscribers: Rc<RefCell<Vec<Effect>>>,
     update_any: Arc<dyn Fn(ScopeId)>,
     value: T,
 }
@@ -31,7 +62,8 @@ impl<T: 'static> Signal<T> {
     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<T: 'static> Signal<T> {
     }
 
     pub fn read(&self) -> Ref<T> {
+        let inner = self.inner.read();
         if let Some(current_scope_id) = current_scope_id() {
-            let mut inner = self.inner.write();
-            if !inner.subscribers.contains(&current_scope_id) {
-                inner.subscribers.push(current_scope_id);
+            let mut subscribers = inner.subscribers.borrow_mut();
+            if !subscribers.contains(&current_scope_id) {
+                subscribers.push(current_scope_id);
+                drop(subscribers);
+                let unsubscriber = current_unsubscriber();
+                inner.subscribers.borrow_mut().push(unsubscriber.scope);
+            }
+        }
+        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(self.inner.read(), |v| &v.value)
+        Ref::map(inner, |v| &v.value)
     }
 
     pub fn write(&self) -> RefMut<T> {
-        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<T: 'static> Signal<T> {
         f(&*write)
     }
 
-    pub fn update<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
+    pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
         let mut write = self.write();
         f(&mut *write)
     }
 }
 
 impl<T: Clone + 'static> Signal<T> {
-    pub fn get(&self) -> T {
+    pub fn value(&self) -> T {
         self.read().clone()
     }
 }
 
-impl<T> std::clone::Clone for Signal<T> {
-    fn clone(&self) -> Self {
-        *self
-    }
-}
-
-impl<T> Copy for Signal<T> {}
-
-impl<T: Display + 'static> Display for Signal<T> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        self.with(|v| Display::fmt(v, f))
-    }
-}
-
-impl<T: Debug + 'static> Debug for Signal<T> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        self.with(|v| Debug::fmt(v, f))
-    }
-}
-
-impl<T: Add<Output = T> + Copy + 'static> std::ops::AddAssign<T> for Signal<T> {
-    fn add_assign(&mut self, rhs: T) {
-        self.set(self.get() + rhs);
-    }
-}
-
-impl<T: Sub<Output = T> + Copy + 'static> std::ops::SubAssign<T> for Signal<T> {
-    fn sub_assign(&mut self, rhs: T) {
-        self.set(self.get() - rhs);
-    }
-}
-
-impl<T: Mul<Output = T> + Copy + 'static> std::ops::MulAssign<T> for Signal<T> {
-    fn mul_assign(&mut self, rhs: T) {
-        self.set(self.get() * rhs);
-    }
-}
-
-impl<T: Div<Output = T> + Copy + 'static> std::ops::DivAssign<T> for Signal<T> {
-    fn div_assign(&mut self, rhs: T) {
-        self.set(self.get() / rhs);
+impl<T: 'static> PartialEq for Signal<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.inner == other.inner
     }
 }

+ 26 - 8
packages/signals/src/rt.rs

@@ -26,14 +26,6 @@ fn current_owner() -> Rc<Owner> {
     }
 }
 
-impl<T> Copy for CopyValue<T> {}
-
-impl<T> Clone for CopyValue<T> {
-    fn clone(&self) -> Self {
-        *self
-    }
-}
-
 pub struct CopyValue<T: 'static> {
     pub value: CopyHandle<T>,
 }
@@ -62,4 +54,30 @@ impl<T: 'static> CopyValue<T> {
     pub fn write(&self) -> RefMut<'_, T> {
         self.value.write()
     }
+
+    pub fn set(&mut self, value: T) {
+        *self.write() = value;
+    }
+
+    pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
+        let write = self.read();
+        f(&*write)
+    }
+
+    pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
+        let mut write = self.write();
+        f(&mut *write)
+    }
+}
+
+impl<T: Clone + 'static> CopyValue<T> {
+    pub fn value(&self) -> T {
+        self.read().clone()
+    }
+}
+
+impl<T: 'static> PartialEq for CopyValue<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.value.ptr_eq(&other.value)
+    }
 }