From e104af7e9ae77b6e0d07eb9f770f26bf008e718a Mon Sep 17 00:00:00 2001 From: Filipe Rodrigues Date: Tue, 27 Feb 2024 18:42:37 +0000 Subject: [PATCH] `Effect` and `WeakEffect` now carry a generic type for the function. --- dynatos-loadable/src/lazy_loadable.rs | 2 +- dynatos-reactive/src/derived.rs | 2 +- dynatos-reactive/src/effect.rs | 129 +++++++++++++++-------- dynatos-reactive/src/signal.rs | 10 +- dynatos-reactive/src/trigger.rs | 16 +-- dynatos-router/src/query_array_signal.rs | 2 +- dynatos-router/src/query_signal.rs | 2 +- dynatos/src/object_attach_effect.rs | 6 +- 8 files changed, 103 insertions(+), 66 deletions(-) diff --git a/dynatos-loadable/src/lazy_loadable.rs b/dynatos-loadable/src/lazy_loadable.rs index 6f6cd89..177b9c2 100644 --- a/dynatos-loadable/src/lazy_loadable.rs +++ b/dynatos-loadable/src/lazy_loadable.rs @@ -43,7 +43,7 @@ pub struct LazyLoadable { load_status: Signal, /// Effect - effect: Effect, + effect: Effect, } impl LazyLoadable { diff --git a/dynatos-reactive/src/derived.rs b/dynatos-reactive/src/derived.rs index e94a85e..f2cd26f 100644 --- a/dynatos-reactive/src/derived.rs +++ b/dynatos-reactive/src/derived.rs @@ -44,7 +44,7 @@ use { /// See the module documentation for more information. pub struct Derived { /// Effect - effect: Effect, + effect: Effect, /// Value value: Signal>, diff --git a/dynatos-reactive/src/effect.rs b/dynatos-reactive/src/effect.rs index e7e1d61..38b265b 100644 --- a/dynatos-reactive/src/effect.rs +++ b/dynatos-reactive/src/effect.rs @@ -3,52 +3,55 @@ //! An effect is a function that is re-run whenever //! one of it's dependencies changes. +// TODO: Downcasting? It isn't trivial due to the usages of `Rc>`, +// which doesn't allow casting to `Rc`, required by `Rc::downcast`. + // Imports use std::{ - cell::RefCell, + cell::{Cell, RefCell}, fmt, hash::Hash, - mem, + marker::Unsize, + ops::CoerceUnsized, rc::{Rc, Weak}, }; thread_local! { /// Effect stack - static EFFECT_STACK: RefCell> = const { RefCell::new(vec![]) }; + static EFFECT_STACK: RefCell>> = const { RefCell::new(vec![]) }; } /// Effect inner -struct Inner { +struct Inner { /// Whether this effect is currently suppressed - suppressed: bool, + suppressed: Cell, /// Effect runner - run: Box, + run: F, } +impl CoerceUnsized> for Inner where F1: CoerceUnsized {} + /// Effect -#[derive(Clone)] -pub struct Effect { +pub struct Effect { /// Inner - inner: Rc>, + inner: Rc>, } -impl Effect { +impl Effect { /// Creates a new computed effect. /// /// Runs the effect once to gather dependencies. - pub fn new(run: F) -> Self + pub fn new(run: F) -> Self where F: Fn() + 'static, { // Create the effect let inner = Inner { - suppressed: false, - run: Box::new(run), - }; - let effect = Self { - inner: Rc::new(RefCell::new(inner)), + suppressed: Cell::new(false), + run, }; + let effect = Self { inner: Rc::new(inner) }; // And run it once to gather dependencies. effect.run(); @@ -59,7 +62,7 @@ impl Effect { /// Tries to create a new effect. /// /// If the effects ends up being inert, returns `None` - pub fn try_new(run: F) -> Option + pub fn try_new(run: F) -> Option where F: Fn() + 'static, { @@ -69,9 +72,11 @@ impl Effect { false => Some(effect), } } +} +impl Effect { /// Downgrades this effect - pub fn downgrade(&self) -> WeakEffect { + pub fn downgrade(&self) -> WeakEffect { WeakEffect { inner: Rc::downgrade(&self.inner), } @@ -94,14 +99,16 @@ impl Effect { } /// Runs the effect - pub fn run(&self) { + pub fn run(&self) + where + F: Fn() + Unsize + 'static, + { // Push the effect, run the closure and pop it EFFECT_STACK.with_borrow_mut(|effects| effects.push(self.downgrade())); // Then run it, if it's not suppressed - let inner = self.inner.borrow(); - if !inner.suppressed { - (inner.run)(); + if !self.inner.suppressed.get() { + (self.inner.run)(); } // And finally pop the effect from the stack @@ -111,61 +118,80 @@ impl Effect { } /// Suppresses this effect from running while calling this function - pub fn suppressed(&self, f: F) -> O + pub fn suppressed(&self, f: F2) -> O where - F: FnOnce() -> O, + F2: FnOnce() -> O, { // Set the suppress flag and run `f` - let last = mem::replace(&mut self.inner.borrow_mut().suppressed, true); + let last_suppressed = self.inner.suppressed.replace(true); let output = f(); // Then restore it - self.inner.borrow_mut().suppressed = last; + self.inner.suppressed.set(last_suppressed); output } } -impl PartialEq for Effect { - fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.inner, &other.inner) +impl PartialEq> for Effect { + fn eq(&self, other: &Effect) -> bool { + self.inner_ptr() == other.inner_ptr() } } -impl Eq for Effect {} +impl Eq for Effect {} -impl Hash for Effect { +impl Clone for Effect { + fn clone(&self) -> Self { + Self { + inner: Rc::clone(&self.inner), + } + } +} + +impl Hash for Effect { fn hash(&self, state: &mut H) { - self.inner.as_ptr().hash(state); + Rc::as_ptr(&self.inner).hash(state); } } -impl fmt::Debug for Effect { +impl fmt::Debug for Effect { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Effect").finish_non_exhaustive() } } +impl CoerceUnsized> for Effect where T: Unsize {} + /// Weak effect /// /// Used to break ownership between a signal and it's subscribers -#[derive(Clone)] -pub struct WeakEffect { +pub struct WeakEffect { /// Inner - inner: Weak>, + inner: Weak>, } -impl WeakEffect { +impl WeakEffect { /// Upgrades this effect - pub fn upgrade(&self) -> Option { + pub fn upgrade(&self) -> Option> { self.inner.upgrade().map(|inner| Effect { inner }) } + /// Returns the pointer of this effect + /// + /// This can be used for creating maps based on equality + pub fn inner_ptr(&self) -> *const () { + Weak::as_ptr(&self.inner).cast() + } + /// Runs this effect, if it exists. /// /// Returns if the effect still existed - pub fn try_run(&self) -> bool { + pub fn try_run(&self) -> bool + where + F: Fn() + Unsize + 'static, + { // Try to upgrade, else return that it was missing let Some(effect) = self.upgrade() else { return false; @@ -176,28 +202,39 @@ impl WeakEffect { } } -impl PartialEq for WeakEffect { - fn eq(&self, other: &Self) -> bool { - Weak::ptr_eq(&self.inner, &other.inner) +impl PartialEq> for WeakEffect { + fn eq(&self, other: &WeakEffect) -> bool { + self.inner_ptr() == other.inner_ptr() } } -impl Eq for WeakEffect {} +impl Eq for WeakEffect {} -impl Hash for WeakEffect { +impl Clone for WeakEffect { + fn clone(&self) -> Self { + Self { + inner: Weak::clone(&self.inner), + } + } +} + + +impl Hash for WeakEffect { fn hash(&self, state: &mut H) { self.inner.as_ptr().hash(state); } } -impl fmt::Debug for WeakEffect { +impl fmt::Debug for WeakEffect { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WeakEffect").finish_non_exhaustive() } } +impl CoerceUnsized> for WeakEffect where T: Unsize {} + /// Returns the current running effect -pub fn running() -> Option { +pub fn running() -> Option> { EFFECT_STACK.with_borrow(|effects| effects.last().cloned()) } diff --git a/dynatos-reactive/src/signal.rs b/dynatos-reactive/src/signal.rs index be0251f..eb49acc 100644 --- a/dynatos-reactive/src/signal.rs +++ b/dynatos-reactive/src/signal.rs @@ -118,17 +118,17 @@ impl fmt::Debug for Signal { /// Types that may be converted into a subscriber pub trait IntoSubscriber { - fn into_subscriber(self) -> WeakEffect; + fn into_subscriber(self) -> WeakEffect; } #[duplicate::duplicate_item( T body; - [ Effect ] [ self.downgrade() ]; - [ &'_ Effect ] [ self.downgrade() ]; - [ WeakEffect ] [ self ]; + [ Effect ] [ self.downgrade() ]; + [ &'_ Effect ] [ self.downgrade() ]; + [ WeakEffect ] [ self ]; )] impl IntoSubscriber for T { - fn into_subscriber(self) -> WeakEffect { + fn into_subscriber(self) -> WeakEffect { body } } diff --git a/dynatos-reactive/src/trigger.rs b/dynatos-reactive/src/trigger.rs index 12b2bae..45e2958 100644 --- a/dynatos-reactive/src/trigger.rs +++ b/dynatos-reactive/src/trigger.rs @@ -12,7 +12,7 @@ use { /// Trigger inner struct Inner { /// Subscribers - subscribers: HashSet, + subscribers: HashSet>, } /// Trigger @@ -92,17 +92,17 @@ impl fmt::Debug for Trigger { /// Types that may be converted into a subscriber pub trait IntoSubscriber { - fn into_subscriber(self) -> WeakEffect; + fn into_subscriber(self) -> WeakEffect; } #[duplicate::duplicate_item( T body; - [ Effect ] [ self.downgrade() ]; - [ &'_ Effect ] [ self.downgrade() ]; - [ WeakEffect ] [ self ]; + [ Effect ] [ self.downgrade() ]; + [ &'_ Effect ] [ self.downgrade() ]; + [ WeakEffect ] [ self ]; )] impl IntoSubscriber for T { - fn into_subscriber(self) -> WeakEffect { + fn into_subscriber(self) -> WeakEffect { body } } @@ -128,7 +128,7 @@ mod test { // Then create the trigger, and ensure it wasn't triggered // by just creating it and adding the subscriber let trigger = Trigger::new(); - trigger.add_subscriber(effect.downgrade()); + trigger.add_subscriber(effect.downgrade() as WeakEffect); assert_eq!(TRIGGERS.get(), 1, "Trigger was triggered early"); // Then trigger and ensure it was triggered @@ -136,7 +136,7 @@ mod test { assert_eq!(TRIGGERS.get(), 2, "Trigger was not triggered"); // Then add the subscriber again and ensure the effect isn't run twice - trigger.add_subscriber(effect.downgrade()); + trigger.add_subscriber(effect.downgrade() as WeakEffect); trigger.trigger(); assert_eq!(TRIGGERS.get(), 3, "Trigger ran effect multiple times"); diff --git a/dynatos-router/src/query_array_signal.rs b/dynatos-router/src/query_array_signal.rs index e42b39b..13847ce 100644 --- a/dynatos-router/src/query_array_signal.rs +++ b/dynatos-router/src/query_array_signal.rs @@ -17,7 +17,7 @@ pub struct QueryArraySignal { inner: Signal>, /// Update effect. - update_effect: Effect, + update_effect: Effect, } impl QueryArraySignal { diff --git a/dynatos-router/src/query_signal.rs b/dynatos-router/src/query_signal.rs index cd1aec1..10c01e7 100644 --- a/dynatos-router/src/query_signal.rs +++ b/dynatos-router/src/query_signal.rs @@ -17,7 +17,7 @@ pub struct QuerySignal { inner: Signal>, /// Update effect. - update_effect: Effect, + update_effect: Effect, } impl QuerySignal { diff --git a/dynatos/src/object_attach_effect.rs b/dynatos/src/object_attach_effect.rs index 4ba1054..964860f 100644 --- a/dynatos/src/object_attach_effect.rs +++ b/dynatos/src/object_attach_effect.rs @@ -12,7 +12,7 @@ use { #[extend::ext(name = ObjectAttachEffect)] pub impl js_sys::Object { /// Attaches an effect to this object - fn attach_effect(&self, effect: Effect) { + fn attach_effect(&self, effect: Effect) { // Get the effects map, or create it, if it doesn't exist // TODO: Use an static anonymous symbol? let prop_name: &str = "__dynatos_effects"; @@ -42,7 +42,7 @@ where /// Attaches an effect to this object. /// /// Returns the object, for chaining - fn with_effect(self, effect: Effect) -> Self { + fn with_effect(self, effect: Effect) -> Self { self.as_ref().attach_effect(effect); self } @@ -51,4 +51,4 @@ where /// A wasm `Effect` type. #[wasm_bindgen] #[expect(dead_code, reason = "We just want to keep the field alive, not use it")] -struct WasmEffect(Effect); +struct WasmEffect(Effect);