// Computed / Memo - derived reactive values
// Compatible with signal module API
///|
/// Create a memoized computed value (internal)
fn[T] create_computed(getter : () -> T) -> Computed[T] {
{ node: ReactiveNode::new(ReactiveFlags::None.to_int()), value: None, getter }
}
///|
/// Get the computed value
/// Automatically tracks dependency if called inside an effect/computed
pub fn[T] Computed::get(self : Computed[T]) -> T {
let flags = self.node.flags.get_value()
let dirty_flag = ReactiveFlags::Dirty.to_int()
let pending_flag = ReactiveFlags::Pending.to_int()
let mutable_flag = ReactiveFlags::Mutable.to_int()
let recursed_check_flag = ReactiveFlags::RecursedCheck.to_int()
// Check if dirty or pending (need to recompute)
let is_dirty = (flags & dirty_flag) != 0
let is_pending = (flags & pending_flag) != 0
// Check if dependencies have changed
let deps_changed = if is_pending {
check_deps_dirty(self.node)
} else {
false
}
if is_dirty || deps_changed {
// Recompute
if update_computed(self) && self.node.subs is Some(subs) {
shallow_propagate(subs)
}
} else if is_pending {
// Clear pending flag if deps not dirty
self.node.flags.set_value(flags & (pending_flag ^ -1))
} else if flags == ReactiveFlags::None.to_int() {
// First computation
self.node.flags.set_value(mutable_flag | recursed_check_flag)
let prev_sub = set_active_sub(Some(self.node))
self.value = Some((self.getter)())
let _ = set_active_sub(prev_sub)
let current_flags = self.node.flags.get_value()
self.node.flags.set_value(current_flags & (recursed_check_flag ^ -1))
}
// Track dependency
if get_active_sub() is Some(sub) {
link(self.node, sub, get_cycle())
}
match self.value {
Some(v) => v
None => panic()
}
}
///|
/// Update computed value, returns true if changed
fn[T] update_computed(c : Computed[T]) -> Bool {
inc_cycle()
c.node.deps_tail = None
c.node.flags.set_value(
ReactiveFlags::Mutable.to_int() | ReactiveFlags::RecursedCheck.to_int(),
)
let prev_sub = set_active_sub(Some(c.node))
let old_value = c.value
let new_value = (c.getter)()
c.value = Some(new_value)
let _ = set_active_sub(prev_sub)
let current_flags = c.node.flags.get_value()
c.node.flags.set_value(
current_flags & (ReactiveFlags::RecursedCheck.to_int() ^ -1),
)
purge_deps(c.node)
match old_value {
Some(old) => not(physical_equal(old, new_value))
None => true
}
}
///|
/// Check if any dependency has changed since last computation
/// Uses last_modified_cycle to handle diamond dependencies correctly
fn check_deps_dirty(node : ReactiveNode) -> Bool {
let dirty_flag = ReactiveFlags::Dirty.to_int()
let pending_flag = ReactiveFlags::Pending.to_int()
let mutable_flag = ReactiveFlags::Mutable.to_int()
let mut current_link = node.deps
while current_link is Some(lnk) {
let dep = lnk.dep
let dep_flags = dep.flags.get_value()
if (dep_flags & mutable_flag) != 0 {
// Check if dep was modified after this link was created
// This handles diamond dependencies where dirty flag may be cleared
if dep.last_modified_cycle > lnk.version {
return true
}
// Also check traditional dirty/pending flags
if (dep_flags & (dirty_flag | pending_flag)) != 0 {
return true
}
}
current_link = lnk.next_dep
}
false
}
///|
/// Peek at current value without tracking
pub fn[T] Computed::peek(self : Computed[T]) -> T? {
self.value
}