use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
use adaptite::{DiagnosticEvent, DiagnosticSubscription, FlushStats, Reactor, signal_in};
use runite::time::sleep;
struct IdleAudit {
flushes: Rc<RefCell<Vec<FlushStats>>>,
_subscription: DiagnosticSubscription,
}
impl IdleAudit {
fn install(reactor: &Reactor) -> Self {
let flushes = Rc::new(RefCell::new(Vec::new()));
let subscription = reactor.subscribe_diagnostics({
let flushes = Rc::clone(&flushes);
move |event| {
if let DiagnosticEvent::FlushFinished { stats, .. } = event {
flushes.borrow_mut().push(stats);
}
}
});
Self {
flushes,
_subscription: subscription,
}
}
fn mark_settled(&self) {
self.flushes.borrow_mut().clear();
}
fn report(&self) -> Verdict {
let flushes = self.flushes.borrow();
Verdict {
flushes: flushes.len(),
empty: flushes.iter().filter(|stats| stats.is_empty()).count(),
effects_run: flushes.iter().map(|stats| stats.effects_run).sum(),
computed_recomputed: flushes.iter().map(|stats| stats.computed_recomputed).sum(),
root_writes: flushes.iter().map(|stats| stats.root_writes).sum(),
}
}
}
#[derive(Debug)]
struct Verdict {
flushes: usize,
empty: usize,
effects_run: u32,
computed_recomputed: u32,
root_writes: u32,
}
impl Verdict {
fn is_quiet(&self) -> bool {
self.flushes == 0
}
fn explain(&self) -> &'static str {
if self.flushes == 0 {
"quiet: the reactive graph did not flush at all. Whatever cost remains is below \
adaptite — look at the runtime and the framework."
} else if self.flushes == self.empty {
"waking without working: flushes ran and did nothing. Something is scheduling a \
reactive job at idle. Subscribe to EffectScheduled to find out what."
} else {
"working: the graph is genuinely recomputing at idle. root_writes says how much of \
it started with a write; ComputedRecomputeStarted names the nodes."
}
}
}
#[runite::main]
async fn main() {
let reactor = Reactor::new();
let _guard = reactor.enter();
let temperature = signal_in(&reactor, 20_i32);
let label = {
let temperature = temperature.clone();
reactor.memo(move || format!("{}C", temperature.get()))
};
let rendered = Rc::new(RefCell::new(Vec::new()));
let render = reactor.effect({
let label = label.clone();
let rendered = Rc::clone(&rendered);
move || rendered.borrow_mut().push(label.get())
});
let audit = IdleAudit::install(&reactor);
reactor.flush_now();
println!("after start-up: {:?}", audit.report());
audit.mark_settled();
sleep(Duration::from_millis(20)).await;
let verdict = audit.report();
println!("\nidle for 20ms -> {}", verdict.explain());
println!(" {verdict:?}");
assert!(
verdict.is_quiet(),
"nothing happened, so nothing should flush"
);
audit.mark_settled();
for _ in 0..5 {
temperature.set(20);
reactor.flush_now();
}
assert!(
audit.report().is_quiet(),
"`set` suppresses an unchanged write before it reaches the graph"
);
audit.mark_settled();
for _ in 0..5 {
temperature.replace(20);
reactor.flush_now();
}
let verdict = audit.report();
println!("\nreplace(same) -> {}", verdict.explain());
println!(" {verdict:?}");
assert!(!verdict.is_quiet());
assert_eq!(verdict.root_writes, 5, "five writes reached the graph");
assert_eq!(
verdict.effects_run, 0,
"and none of them was worth re-rendering for"
);
assert_eq!(
verdict.computed_recomputed, 5,
"the memo recomputed each time; only equality kept the effect from re-running"
);
audit.mark_settled();
temperature.set(21);
reactor.flush_now();
let verdict = audit.report();
println!("\nreal change -> {}", verdict.explain());
println!(" {verdict:?}");
assert_eq!(verdict.effects_run, 1);
println!("\nrendered: {:?}", rendered.borrow());
render.dispose();
}