use alloc::rc::Rc;
use core::cell::RefCell;
use crate::{EffectHandle, Reactor, current};
#[track_caller]
pub fn watch<S>(
source: impl Fn() -> S + 'static,
handler: impl Fn(&S, Option<&S>) + 'static,
) -> EffectHandle
where
S: Clone + PartialEq + 'static,
{
current().watch(source, handler)
}
#[track_caller]
pub fn watch_in<S>(
reactor: &Reactor,
source: impl Fn() -> S + 'static,
handler: impl Fn(&S, Option<&S>) + 'static,
) -> EffectHandle
where
S: Clone + PartialEq + 'static,
{
reactor.watch(source, handler)
}
impl Reactor {
#[track_caller]
pub fn watch<S>(
&self,
source: impl Fn() -> S + 'static,
handler: impl Fn(&S, Option<&S>) + 'static,
) -> EffectHandle
where
S: Clone + PartialEq + 'static,
{
let gated = self.memo(source);
let previous: Rc<RefCell<Option<S>>> = Rc::new(RefCell::new(None));
self.effect(move || {
let new = gated.get();
crate::untrack(|| {
let mut previous = previous.borrow_mut();
handler(&new, previous.as_ref());
*previous = Some(new);
});
})
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::rc::Rc;
use runite::{queue_macrotask, run};
use crate::{Reactor, signal_in};
#[test]
fn watch_fires_on_change_with_old_and_new_values() {
let seen = Rc::new(RefCell::new(Vec::new()));
queue_macrotask({
let seen = Rc::clone(&seen);
move || {
let reactor = Reactor::new();
let value = signal_in(&reactor, 1i32);
let handle = reactor.watch(
{
let value = value.clone();
move || value.get()
},
{
let seen = Rc::clone(&seen);
move |new: &i32, old: Option<&i32>| {
seen.borrow_mut().push((old.copied(), *new));
}
},
);
reactor.flush_now();
value.set(2);
reactor.flush_now();
value.set(2); reactor.flush_now();
value.set(5);
reactor.flush_now();
handle.leak();
}
});
run();
assert_eq!(&*seen.borrow(), &[(None, 1), (Some(1), 2), (Some(2), 5)],);
}
#[test]
fn watch_source_is_equality_gated_and_handler_is_untracked() {
let runs = Rc::new(RefCell::new(0usize));
queue_macrotask({
let runs = Rc::clone(&runs);
move || {
let reactor = Reactor::new();
let numerator = signal_in(&reactor, 4i32);
let side_input = signal_in(&reactor, 0i32);
let handle = reactor.watch(
{
let numerator = numerator.clone();
move || numerator.get() % 2
},
{
let runs = Rc::clone(&runs);
let side_input = side_input.clone();
move |_new: &i32, _old: Option<&i32>| {
let _ = side_input.get();
*runs.borrow_mut() += 1;
}
},
);
reactor.flush_now();
assert_eq!(*runs.borrow(), 1, "immediate first invocation");
numerator.set(6);
reactor.flush_now();
assert_eq!(*runs.borrow(), 1);
side_input.set(99);
reactor.flush_now();
assert_eq!(*runs.borrow(), 1);
numerator.set(7);
reactor.flush_now();
assert_eq!(*runs.borrow(), 2);
handle.leak();
}
});
run();
}
}