1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//! Computed — lazy, memoized derivations over signals and proxy fields.
//!
//! A [`Computed`] is a thin wrapper over a lazy [`crate::reactive::effect`]
//! with a custom scheduler. The effect body fills a cache; the scheduler
//! flips a `dirty` bit and re-triggers the computed's own synthetic signal
//! key. Callers of `get()` read the cache on clean reads and re-run the
//! effect on dirty reads, so sources are only re-evaluated when something
//! actually reads the derived value after an input changed.
//!
//! Drop behavior: releasing a `Computed` releases its internal effect, so
//! scopes of parent effects stay tidy without a manual teardown step.
use std::cell::RefCell;
use std::rc::Rc;
use crate::reactive::{
effect_with, next_signal_id, release, run_now, track_signal, trigger_signal, EffectId,
EffectOptions, SignalId,
};
struct State<T> {
cached: Option<T>,
dirty: bool,
}
/// Memoized derivation. Construct via [`computed`].
pub struct Computed<T: 'static> {
id: SignalId,
effect_id: EffectId,
state: Rc<RefCell<State<T>>>,
}
/// Build a memoized derivation. `f` is run the first time `Computed::get`
/// is called (lazy) and re-run whenever one of its inputs changes *and*
/// someone reads the result again.
pub fn computed<T: Clone + 'static>(f: impl Fn() -> T + 'static) -> Computed<T> {
let id = next_signal_id();
let state: Rc<RefCell<State<T>>> = Rc::new(RefCell::new(State {
cached: None,
dirty: true,
}));
let state_for_body = state.clone();
let state_for_sched = state.clone();
let effect_id = effect_with(
move || {
let v = f();
let mut s = state_for_body.borrow_mut();
s.cached = Some(v);
s.dirty = false;
},
EffectOptions {
lazy: true,
scheduler: Some(Rc::new(move |_eid: EffectId| {
state_for_sched.borrow_mut().dirty = true;
// Tell our own subscribers the value has (potentially)
// changed. They'll call back into `get()` and we'll rerun
// the effect lazily if dirty is still true at that point.
trigger_signal(id);
})),
},
);
Computed {
id,
effect_id,
state,
}
}
impl<T: Clone + 'static> Computed<T> {
/// Subscribe the current effect and return the latest value. Runs the
/// derivation only if a dep has changed since the last read.
pub fn get(&self) -> T {
let dirty = self.state.borrow().dirty;
if dirty {
run_now(self.effect_id);
}
track_signal(self.id);
self.state
.borrow()
.cached
.clone()
.expect("computed body failed to populate the cache")
}
}
impl<T: 'static> Computed<T> {
pub fn id(&self) -> SignalId {
self.id
}
}
impl<T: 'static> Drop for Computed<T> {
fn drop(&mut self) {
release(self.effect_id);
}
}