mirror of
https://github.com/Zenithsiz/dynatos.git
synced 2026-02-08 21:09:52 +00:00
Effect and WeakEffect now carry a generic type for the function.
This commit is contained in:
@@ -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> {
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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()>);
|
||||
|
||||
Reference in New Issue
Block a user