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 prior = previous.borrow().clone();
handler(&new, prior.as_ref());
*previous.borrow_mut() = 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));
let observed = Rc::new(RefCell::new(Vec::new()));
queue_macrotask({
let runs = Rc::clone(&runs);
let observed = Rc::clone(&observed);
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;
}
},
);
let record = |stage: &'static str| {
observed.borrow_mut().push((stage, *runs.borrow()));
};
reactor.flush_now();
record("first invocation");
numerator.set(6);
reactor.flush_now();
record("source recomputed to an equal value");
side_input.set(99);
reactor.flush_now();
record("handler's untracked read was written");
numerator.set(7);
reactor.flush_now();
record("parity changed");
handle.leak();
}
});
run();
assert_eq!(
&*observed.borrow(),
&[
("first invocation", 1),
("source recomputed to an equal value", 1),
("handler's untracked read was written", 1),
("parity changed", 2),
]
);
}
}