46#include <unordered_set>
65 const std::vector<const Node*>& seeds) {
66 std::vector<const Node*> out;
67 std::unordered_set<const Node*> visited;
68 std::vector<const Node*> stack(seeds.begin(), seeds.end());
70 while (!stack.empty()) {
71 const Node* n = stack.back();
73 if (!n || !visited.insert(n).second)
continue;
85 walk_observers_(n, [&](
const Node* obs) {
86 if (obs) stack.push_back(obs);
96 static std::string
to_dot(
const std::vector<const Node*>& seeds,
97 std::string_view graph_name =
"reactive") {
98 std::ostringstream os;
99 os <<
"digraph \"" << escape_(graph_name) <<
"\" {\n";
100 os <<
" rankdir=LR;\n";
101 os <<
" node [shape=box, style=rounded, fontname=\"monospace\"];\n";
106 for (
const Node* n : nodes) {
107 os <<
" \"" <<
reinterpret_cast<std::uintptr_t
>(n) <<
"\" "
108 <<
"[label=\"" << escape_(label_for_(n))
109 <<
"\", " << style_for_(n) <<
"];\n";
115 for (
const Node* n : nodes) {
116 n->for_each_source([&](
const Edge& e) {
118 os <<
" \"" <<
reinterpret_cast<std::uintptr_t
>(e.
source)
119 <<
"\" -> \"" <<
reinterpret_cast<std::uintptr_t
>(e.
observer)
132 static std::string
to_json(
const std::vector<const Node*>& seeds) {
133 std::ostringstream os;
136 os <<
"{\"nodes\":[";
138 for (
const Node* n : nodes) {
139 if (!first) os <<
',';
141 os <<
"{\"id\":" <<
reinterpret_cast<std::uintptr_t
>(n)
142 <<
",\"kind\":\"" << kind_name_(n->kind())
143 <<
"\",\"state\":\"" << state_name_(n->state())
144 <<
"\",\"depth\":" << n->depth()
145 <<
",\"version\":" << n->version()
146 <<
",\"name\":\"" << escape_json_(n->debug_name()) <<
"\"}";
148 os <<
"],\"edges\":[";
151 for (
const Node* n : nodes) {
152 n->for_each_source([&](
const Edge& e) {
154 if (!first) os <<
',';
156 os <<
"{\"from\":" <<
reinterpret_cast<std::uintptr_t
>(e.
source)
157 <<
",\"to\":" <<
reinterpret_cast<std::uintptr_t
>(e.
observer)
216 auto next = std::make_shared<FlushTraceFn>([tracer = std::move(tracer),
217 last_pull = std::chrono::steady_clock::time_point{}](
221 bool changed)
mutable {
228 last_pull = std::chrono::steady_clock::now();
230 const auto now = std::chrono::steady_clock::now();
231 ev.
duration_us = std::chrono::duration_cast<std::chrono::microseconds>(
232 now - last_pull).count();
267 std::shared_ptr<FlushTraceFn> previous_;
276 static std::string
to_text(
const std::vector<const Node*>& seeds) {
277 std::ostringstream os;
279 os <<
"[" << kind_name_(n->kind()) <<
"]"
280 <<
" " << n->effective_debug_name()
281 <<
" depth=" << n->depth()
282 <<
" v=" << n->version()
283 <<
" state=" << state_name_(n->state()) <<
'\n';
289 static const char* kind_name_(
NodeKind k)
noexcept {
298 static const char* state_name_(
NodeState s)
noexcept {
308 static std::string label_for_(
const Node* n) {
309 std::ostringstream os;
310 os << kind_name_(n->kind()) <<
'\n';
311 os << n->effective_debug_name() <<
'\n';
312 os <<
"d=" << n->depth()
313 <<
" v=" << n->version()
314 <<
" " << state_name_(n->state());
318 static const char* style_for_(
const Node* n)
noexcept {
320 case NodeKind::Source:
return "fillcolor=\"#cde4ff\", style=\"rounded,filled\"";
327 static std::string escape_(std::string_view s) {
329 out.reserve(s.size());
331 if (c ==
'"' || c ==
'\\') { out.push_back(
'\\'); out.push_back(c); }
332 else if (c ==
'\n') { out +=
"\\n"; }
333 else { out.push_back(c); }
338 static std::string escape_json_(std::string_view s) {
340 out.reserve(s.size());
343 case '"': out +=
"\\\"";
break;
344 case '\\': out +=
"\\\\";
break;
345 case '\n': out +=
"\\n";
break;
346 case '\r': out +=
"\\r";
break;
347 case '\t': out +=
"\\t";
break;
349 if (
static_cast<unsigned char>(c) < 0x20) {
350 constexpr char hex[] =
"0123456789abcdef";
352 const auto value =
static_cast<unsigned char>(c);
353 out.push_back(hex[value >> 4]);
354 out.push_back(hex[value & 0x0f]);
369 static void walk_observers_(
const Node* n, F&& f) {
373 const_cast<Node*
>(n)->for_each_observer([&](
const Edge& e) {
ScopedTracer(FlushTracer tracer)
Definition inspector.hpp:256
void assert_on_graph_thread() const noexcept
Definition graph.hpp:155
ScopedTracer(FlushTracer tracer)
Definition inspector.hpp:256
~ScopedTracer()
Definition inspector.hpp:261
ScopedTracer & operator=(const ScopedTracer &)=delete
ScopedTracer(const ScopedTracer &)=delete
Lightweight diagnostics entry point.
Definition inspector.hpp:54
static std::vector< const Node * > reachable_from(const std::vector< const Node * > &seeds)
Collect every node reachable from seeds through either direction of the dependency graph (upstream so...
Definition inspector.hpp:64
static void install_flush_tracer(FlushTracer tracer)
Install a tracer.
Definition inspector.hpp:201
std::function< void(const FlushEvent &)> FlushTracer
Definition inspector.hpp:192
static bool has_flush_tracer() noexcept
Definition inspector.hpp:245
static void clear_flush_tracer() noexcept
Definition inspector.hpp:240
static std::string to_dot(const std::vector< const Node * > &seeds, std::string_view graph_name="reactive")
Emit the reachable subgraph as a Graphviz DOT document.
Definition inspector.hpp:96
static std::string to_text(const std::vector< const Node * > &seeds)
Human-readable one-line-per-node summary, useful inside gdb or during ad-hoc printf debugging.
Definition inspector.hpp:276
static std::string to_json(const std::vector< const Node * > &seeds)
Emit the reachable subgraph as a minimal JSON document.
Definition inspector.hpp:132
Common base for every node participating in the reactive graph.
Definition node.hpp:136
static Graph & graph() noexcept
Returns the process-wide singleton Graph this node belongs to.
Definition graph.inl:30
void for_each_source(F &&f) const
Iterate the source list (used by a Derivation in pull() to compare each upstream's current version ag...
Definition node.hpp:243
Definition computed.hpp:60
NodeState
Definition node.hpp:102
@ Computing
Definition node.hpp:106
@ Clean
Definition node.hpp:103
@ MaybeDirty
Definition node.hpp:104
@ Dirty
Definition node.hpp:105
NodeKind
Definition node.hpp:87
@ Derivation
Definition node.hpp:89
@ Reaction
Definition node.hpp:90
@ Source
Definition node.hpp:88
std::shared_ptr< FlushTraceFn > & flush_trace_hook_() noexcept
A single event emitted by the Graph during flush.
Definition inspector.hpp:171
A single dependency edge: (upstream source) -> (downstream observer).
Definition node.hpp:113
Node * observer
The downstream node that depends on it.
Definition node.hpp:115
std::uint64_t observed_version
Upstream version observed at the moment this edge was established or last confirmed.
Definition node.hpp:130
Node * source
The upstream node being observed.
Definition node.hpp:114
A single event emitted by the Graph during flush.
Definition inspector.hpp:171
Phase
Definition inspector.hpp:172
@ Recomputed
pull ran; changed = whether value moved
Definition inspector.hpp:177
@ Pull
about to pull node
Definition inspector.hpp:175
long long duration_us
Elapsed wall-clock microseconds between the matching Pull and this Recomputed event.
Definition inspector.hpp:189
bool changed
valid for Phase::Recomputed
Definition inspector.hpp:184
int round
1-based round index
Definition inspector.hpp:183
const Node * node
null for FlushBegin / FlushEnd / Round boundaries
Definition inspector.hpp:182
Phase phase
Definition inspector.hpp:181