Effect and WeakEffect now carry a generic type for the function.

This commit is contained in:
2024-02-27 18:42:37 +00:00
parent a76c785063
commit e104af7e9a
8 changed files with 103 additions and 66 deletions

View File

@@ -43,7 +43,7 @@ pub struct LazyLoadable<T, E> {
load_status: Signal<LoadStatus>,
/// Effect
effect: Effect,
effect: Effect<dyn Fn()>,
}
impl<T, E> LazyLoadable<T, E> {

View File

@@ -44,7 +44,7 @@ use {
/// See the module documentation for more information.
pub struct Derived<T> {
/// Effect
effect: Effect,
effect: Effect<dyn Fn()>,
/// Value
value: Signal<Option<T>>,

View File

@@ -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<Inner<dyn Fn()>>`,
// which doesn't allow casting to `Rc<dyn Any>`, 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<Vec<WeakEffect>> = const { RefCell::new(vec![]) };
static EFFECT_STACK: RefCell<Vec<WeakEffect<dyn Fn()>>> = const { RefCell::new(vec![]) };
}
/// Effect inner
struct Inner {
struct Inner<F: ?Sized> {
/// Whether this effect is currently suppressed
suppressed: bool,
suppressed: Cell<bool>,
/// Effect runner
run: Box<dyn Fn()>,
run: F,
}
impl<F1: ?Sized, F2: ?Sized> CoerceUnsized<Inner<F2>> for Inner<F1> where F1: CoerceUnsized<F2> {}
/// Effect
#[derive(Clone)]
pub struct Effect {
pub struct Effect<F: ?Sized> {
/// Inner
inner: Rc<RefCell<Inner>>,
inner: Rc<Inner<F>>,
}
impl Effect {
impl<F> Effect<F> {
/// Creates a new computed effect.
///
/// Runs the effect once to gather dependencies.
pub fn new<F>(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<F>(run: F) -> Option<Self>
pub fn try_new(run: F) -> Option<Self>
where
F: Fn() + 'static,
{
@@ -69,9 +72,11 @@ impl Effect {
false => Some(effect),
}
}
}
impl<F: ?Sized> Effect<F> {
/// Downgrades this effect
pub fn downgrade(&self) -> WeakEffect {
pub fn downgrade(&self) -> WeakEffect<F> {
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<dyn Fn()> + '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<F, O>(&self, f: F) -> O
pub fn suppressed<F2, O>(&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<F1: ?Sized, F2: ?Sized> PartialEq<Effect<F2>> for Effect<F1> {
fn eq(&self, other: &Effect<F2>) -> bool {
self.inner_ptr() == other.inner_ptr()
}
}
impl Eq for Effect {}
impl<F: ?Sized> Eq for Effect<F> {}
impl Hash for Effect {
impl<F: ?Sized> Clone for Effect<F> {
fn clone(&self) -> Self {
Self {
inner: Rc::clone(&self.inner),
}
}
}
impl<F: ?Sized> Hash for Effect<F> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.inner.as_ptr().hash(state);
Rc::as_ptr(&self.inner).hash(state);
}
}
impl fmt::Debug for Effect {
impl<F: ?Sized> fmt::Debug for Effect<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Effect").finish_non_exhaustive()
}
}
impl<T: ?Sized, U: ?Sized> CoerceUnsized<Effect<U>> for Effect<T> where T: Unsize<U> {}
/// Weak effect
///
/// Used to break ownership between a signal and it's subscribers
#[derive(Clone)]
pub struct WeakEffect {
pub struct WeakEffect<F: ?Sized> {
/// Inner
inner: Weak<RefCell<Inner>>,
inner: Weak<Inner<F>>,
}
impl WeakEffect {
impl<F: ?Sized> WeakEffect<F> {
/// Upgrades this effect
pub fn upgrade(&self) -> Option<Effect> {
pub fn upgrade(&self) -> Option<Effect<F>> {
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<dyn Fn()> + '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<F1: ?Sized, F2: ?Sized> PartialEq<WeakEffect<F2>> for WeakEffect<F1> {
fn eq(&self, other: &WeakEffect<F2>) -> bool {
self.inner_ptr() == other.inner_ptr()
}
}
impl Eq for WeakEffect {}
impl<F: ?Sized> Eq for WeakEffect<F> {}
impl Hash for WeakEffect {
impl<F: ?Sized> Clone for WeakEffect<F> {
fn clone(&self) -> Self {
Self {
inner: Weak::clone(&self.inner),
}
}
}
impl<F: ?Sized> Hash for WeakEffect<F> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.inner.as_ptr().hash(state);
}
}
impl fmt::Debug for WeakEffect {
impl<F: ?Sized> fmt::Debug for WeakEffect<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WeakEffect").finish_non_exhaustive()
}
}
impl<T: ?Sized, U: ?Sized> CoerceUnsized<WeakEffect<U>> for WeakEffect<T> where T: Unsize<U> {}
/// Returns the current running effect
pub fn running() -> Option<WeakEffect> {
pub fn running() -> Option<WeakEffect<dyn Fn()>> {
EFFECT_STACK.with_borrow(|effects| effects.last().cloned())
}

View File

@@ -118,17 +118,17 @@ impl<T: fmt::Debug> fmt::Debug for Signal<T> {
/// Types that may be converted into a subscriber
pub trait IntoSubscriber {
fn into_subscriber(self) -> WeakEffect;
fn into_subscriber(self) -> WeakEffect<dyn Fn()>;
}
#[duplicate::duplicate_item(
T body;
[ Effect ] [ self.downgrade() ];
[ &'_ Effect ] [ self.downgrade() ];
[ WeakEffect ] [ self ];
[ Effect<dyn Fn()> ] [ self.downgrade() ];
[ &'_ Effect<dyn Fn()> ] [ self.downgrade() ];
[ WeakEffect<dyn Fn()> ] [ self ];
)]
impl IntoSubscriber for T {
fn into_subscriber(self) -> WeakEffect {
fn into_subscriber(self) -> WeakEffect<dyn Fn()> {
body
}
}

View File

@@ -12,7 +12,7 @@ use {
/// Trigger inner
struct Inner {
/// Subscribers
subscribers: HashSet<WeakEffect>,
subscribers: HashSet<WeakEffect<dyn Fn()>>,
}
/// 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<dyn Fn()>;
}
#[duplicate::duplicate_item(
T body;
[ Effect ] [ self.downgrade() ];
[ &'_ Effect ] [ self.downgrade() ];
[ WeakEffect ] [ self ];
[ Effect<dyn Fn()> ] [ self.downgrade() ];
[ &'_ Effect<dyn Fn()> ] [ self.downgrade() ];
[ WeakEffect<dyn Fn()> ] [ self ];
)]
impl IntoSubscriber for T {
fn into_subscriber(self) -> WeakEffect {
fn into_subscriber(self) -> WeakEffect<dyn Fn()> {
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<dyn Fn()>);
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<dyn Fn()>);
trigger.trigger();
assert_eq!(TRIGGERS.get(), 3, "Trigger ran effect multiple times");

View File

@@ -17,7 +17,7 @@ pub struct QueryArraySignal<T> {
inner: Signal<Vec<T>>,
/// Update effect.
update_effect: Effect,
update_effect: Effect<dyn Fn()>,
}
impl<T> QueryArraySignal<T> {

View File

@@ -17,7 +17,7 @@ pub struct QuerySignal<T> {
inner: Signal<Option<T>>,
/// Update effect.
update_effect: Effect,
update_effect: Effect<dyn Fn()>,
}
impl<T> QuerySignal<T> {

View File

@@ -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<dyn Fn()>) {
// 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<dyn Fn()>) -> 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<dyn Fn()>);