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
100
101
102
103
104
105
106
107
108
109
110
111
use std::{collections::HashSet, panic::Location, sync::Arc};
use uuid::Uuid;
/// Type-erased trait for dependency graph introspection.
///
/// This enables recursive traversal of the dependency tree regardless of
/// the concrete value types of each cell.
pub trait DepNode: Send + Sync {
fn id(&self) -> Uuid;
fn name(&self) -> Option<String>;
/// Returns the dependencies of this node.
fn deps(&self) -> Vec<Arc<dyn DepNode>>;
/// The scheduler's per-node height cache slot, if any. Cells expose their
/// `CellInner::height_cache`; other nodes default to `None` and are
/// recomputed each time. Lets the scheduler memoize propagation height
/// (`1 + max(dep.height)`) across ticks, invalidated by the topology epoch.
#[cfg(feature = "scheduler")]
fn height_cache(&self) -> Option<&std::sync::atomic::AtomicU64> {
None
}
/// Whether the scheduler should skip last-write-wins coalescing for this
/// node — enqueuing every notify as a distinct height-ordered op so event
/// semantics (scan/pairwise/merge and hand-rolled stateful maps) survive a
/// batch. Cells expose their per-cell flag; other nodes default to `false`.
#[cfg(feature = "scheduler")]
fn no_coalesce(&self) -> bool {
false
}
/// Returns the number of active subscribers to this node.
fn subscriber_count(&self) -> usize {
0
}
/// Returns the number of subscription guards owned by this node.
fn owned_count(&self) -> usize {
0
}
/// Returns the Debug-formatted current value, if available.
fn value_debug(&self) -> Option<String> {
None
}
/// Returns the source location where this cell was created.
fn caller(&self) -> Option<&'static Location<'static>> {
None
}
fn display_name(&self) -> String {
self.name()
.unwrap_or_else(|| format!("Cell({})", &self.id().to_string()[..8]))
}
fn dependency_count(&self) -> usize {
self.deps().len()
}
fn has_dependencies(&self) -> bool {
!self.deps().is_empty()
}
fn dependency_tree(&self) -> String
where
Self: Sized,
{
fn write_tree(
node: &dyn DepNode,
out: &mut String,
prefix: &str,
visited: &mut HashSet<Uuid>,
) {
use std::fmt::Write;
let _ = writeln!(out, "{}", node.display_name());
if visited.contains(&node.id()) {
let _ = writeln!(out, "{}(cycle)", prefix);
return;
}
visited.insert(node.id());
let deps = node.deps();
for (i, dep) in deps.iter().enumerate() {
let is_last = i == deps.len() - 1;
let connector = if is_last { "└─ " } else { "├─ " };
let child_prefix = if is_last {
format!("{} ", prefix)
} else {
format!("{}│ ", prefix)
};
let _ = write!(out, "{}{}", prefix, connector);
write_tree(dep.as_ref(), out, &child_prefix, visited);
}
}
let mut out = String::new();
write_tree(self, &mut out, "", &mut HashSet::new());
out
}
fn print_dependency_tree(&self)
where
Self: Sized,
{
print!("{}", self.dependency_tree());
}
}