use std::any::Any;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use super::runtime::{NodeData, NodeId, ReactiveNode, Scope};
use super::scheduler;
use super::signal::ReadSignal;
use super::{untrack, with_runtime};
pub fn computed<T: 'static + Clone + PartialEq>(
mut f: impl FnMut() -> T + 'static,
) -> ReadSignal<T> {
let initial = untrack(&mut f);
let value: Rc<RefCell<dyn Any>> = Rc::new(RefCell::new(initial));
type ComputeCell = Rc<RefCell<Option<Box<dyn FnMut()>>>>;
let compute_cell: ComputeCell = Rc::new(RefCell::new(None));
let compute_cell_clone = compute_cell.clone();
let trampoline: Rc<RefCell<dyn FnMut()>> = Rc::new(RefCell::new(move || {
let mut taken = compute_cell_clone.borrow_mut().take();
if let Some(ref mut inner) = taken {
inner();
}
*compute_cell_clone.borrow_mut() = taken;
}));
let needs_warning = with_runtime(|rt| rt.current_owner().is_none());
if needs_warning {
super::warn_no_owner("computed()");
}
let node_id = with_runtime(|rt| {
let owner = rt.current_owner().unwrap_or_else(|| {
let detached = rt.owners.insert(Scope::new(None));
rt.owner_stack.push(detached);
detached
});
let id = rt.nodes.insert(ReactiveNode {
owner,
data: NodeData::Computed {
value: value.clone(),
compute: trampoline,
},
sources: Default::default(),
subscribers: Default::default(),
arc_sources: Vec::new(),
});
if let Some(o) = rt.owners.get_mut(owner) {
o.nodes.push(id);
}
id
});
let value_clone = value.clone();
*compute_cell.borrow_mut() = Some(Box::new(move || {
let new = f();
let changed = {
let borrow = value_clone.borrow();
let old: &T = borrow
.downcast_ref::<T>()
.expect("computed: type mismatch on recompute");
old != &new
};
if changed {
{
let mut borrow = value_clone.borrow_mut();
let slot = borrow
.downcast_mut::<T>()
.expect("computed: type mismatch on write-back");
*slot = new;
}
let subscribers: Vec<NodeId> = with_runtime(|rt| {
rt.nodes
.get(node_id)
.map(|n| n.subscribers.iter().copied().collect())
.unwrap_or_default()
});
for sub in subscribers {
scheduler::schedule(sub);
}
}
}));
scheduler::schedule(node_id);
scheduler::flush();
ReadSignal {
id: node_id,
_ty: PhantomData,
}
}