45 while (handles_head_) {
46 auto* handle = handles_head_;
47 handles_head_ = handle->next_;
48 handle->node_ =
nullptr;
49 handle->previous_ = handle->next_ =
nullptr;
52 while (observers_head_ !=
nullptr) {
53 Edge* e = observers_head_;
65 edge.source = &source;
67 edge.observed_version = source.version_;
70 edge.prev_observer =
nullptr;
71 edge.next_observer = source.observers_head_;
72 if (source.observers_head_) source.observers_head_->prev_observer = &edge;
73 source.observers_head_ = &edge;
76 edge.prev_source =
nullptr;
77 edge.next_source = sources_head_;
78 if (sources_head_) sources_head_->prev_source = &edge;
79 sources_head_ = &edge;
83 if (source.depth_ + 1 > depth_) {
84 depth_ = source.depth_ + 1;
90 Node* src = edge.source;
92 if (edge.prev_observer) edge.prev_observer->next_observer = edge.next_observer;
97 if (edge.prev_source) edge.prev_source->next_source = edge.next_source;
98 else sources_head_ = edge.next_source;
99 if (edge.next_source) edge.next_source->prev_source = edge.prev_source;
101 edge.source =
nullptr;
102 edge.observer =
nullptr;
103 edge.next_observer = edge.prev_observer =
nullptr;
104 edge.next_source = edge.prev_source =
nullptr;
108 while (sources_head_ !=
nullptr) {
136 std::vector<Node*> local_storage;
137 std::vector<Node*>* stack =
nullptr;
138 bool owns_global =
false;
139 if (!g.color_in_use_) {
140 g.color_in_use_ =
true;
142 stack = &g.color_stack_;
145 stack = &local_storage;
146 local_storage.reserve(8);
151 Graph* g;
bool owned;
152 ~Release() {
if (owned) g->color_in_use_ =
false; }
153 } release{&g, owns_global};
155 stack->push_back(
this);
156 while (!stack->empty()) {
157 Node* cur = stack->back();
161 Node* obs = e->observer;
164 stack->push_back(obs);
179 if (batch_depth_ == 0 && !flushing_) {
196 if (flushing_)
return;
204 g->flushing_ =
false;
214 const auto trace_phase = [&](
int phase,
const Node* node,
int round,
bool changed) {
217 (*trace)(phase, node, round, changed);
227 const Node* node,
int round,
bool changed) {
231 node ? node->effective_debug_name() : std::string{},
239 trace_phase(0 ,
nullptr, 0,
false);
245 const detail::NodeHandle& node,
bool changed) {
247 trace_phase(phase, node.get(), rounds, changed);
248 if (node) publish_phase(category, node.get(), rounds, changed);
251 while (!pending_.empty()) {
252 if (++rounds > kMaxFlushRounds) {
257 std::string detail =
"reactive::Graph::flush exceeded "
258 + std::to_string(kMaxFlushRounds)
259 +
" rounds -- likely a circular dependency. "
260 +
"Pending nodes still dirty:";
262 for (
const auto& n : pending_) {
264 if (printed++ >= 16) { detail +=
" ...";
break; }
266 detail += n->effective_debug_name();
268 for (
const auto& n : pending_)
if (n) n->queued_ =
false;
275 round_.swap(pending_);
276 for (
const auto& n : round_)
if (n) n->queued_ =
false;
280 std::sort(round_.begin(), round_.end(),
281 [](
const auto& a,
const auto& b) {
282 if (!a || !b) return !a && static_cast<bool>(b);
283 if (a->depth() != b->depth()) return a->depth() < b->depth();
284 return a->node_id_ < b->node_id_;
287 trace_phase(1 ,
nullptr, rounds,
false);
290 for (
const auto& n : round_) {
301 const bool changed =
pull(*n);
305 trace_phase(5 ,
nullptr, rounds,
false);
310 trace_phase(6 ,
nullptr, rounds,
false);
346 bool has_unsettled_parent =
false;
350 if (!has_unsettled_parent)
return pull_settle_(n);
356 explicit Frame(
Node* target) noexcept : node(target) {}
358 detail::NodeHandle node;
359 bool entered =
false;
361 std::vector<Frame> work;
363 std::vector<Frame>& work;
365 for (
const auto& frame : work) {
366 if (frame.node && frame.entered) frame.node->resolving_ =
false;
370 work.emplace_back(&n);
371 while (!work.empty()) {
372 if (!work.back().node) { work.pop_back();
continue; }
373 auto& frame = work.back();
374 Node& current = *frame.node;
375 if (!frame.entered) {
376 if (current.resolving_) {
378 for (
const auto& entry : work) {
379 if (!entry.node)
continue;
380 if (!path.empty()) path +=
" -> ";
381 path += entry.node->effective_debug_name();
384 "reactive::Graph::pull detected an unresolved dependency cycle: " + path);
386 current.resolving_ =
true;
387 frame.entered =
true;
390 Node* unresolved =
nullptr;
397 work.emplace_back(unresolved);
401 auto ready = std::move(frame.node);
402 current.resolving_ =
false;
404 const bool changed = pull_settle_(*ready);
405 if (work.empty())
return changed;
415inline bool Graph::pull_settle_(
Node& n) {
421 std::size_t cycle_start = pulling_stack_.size();
422 for (std::size_t i = 0; i < pulling_stack_.size(); ++i) {
423 if (pulling_stack_[i].get() == &n) { cycle_start = i;
break; }
425 for (std::size_t i = cycle_start; i < pulling_stack_.size(); ++i) {
426 if (!pulling_stack_[i])
continue;
427 path += pulling_stack_[i]->effective_debug_name();
431 throw CircularDependencyError(
432 "reactive::Graph::pull detected re-entrant computation; cycle path: "
438 bool any_upstream_changed =
false;
441 e.source->version() != e.observed_version) {
442 any_upstream_changed =
true;
445 if (!any_upstream_changed) {
457 detail::NodeHandle target{&n};
458 pulling_stack_.emplace_back(&n);
460 std::vector<detail::NodeHandle>* stack;
461 ~StackGuard() {
if (stack) stack->pop_back(); }
462 } guard{&pulling_stack_};
465 bool changed =
false;
473 if (!target)
return false;
Thrown when flush detects a dependency cycle.
Definition graph.hpp:63
Process-wide singleton reactive graph (accessed via Node::graph()).
Definition graph.hpp:139
void on_source_changed(Node &src)
Called after a Source has committed a new value: bumps its version and colors the downstream MaybeDir...
Definition graph.inl:175
void enqueue_dirty(Node &n)
Enqueue a node into the current flush round's "pending" set.
Definition graph.hpp:203
friend class Node
Definition graph.hpp:145
bool pull(Node &n)
Force-evaluate a single node if it is Dirty / MaybeDirty.
Definition graph.inl:342
void assert_on_graph_thread() const noexcept
Definition graph.hpp:155
void flush()
Evaluate every dirty node in topological order.
Definition graph.inl:194
Common base for every node participating in the reactive graph.
Definition node.hpp:136
const std::string & effective_debug_name() const
A non-empty debug label for diagnostic output.
Definition node.hpp:172
void mark_dirty() noexcept
Escalate state to Dirty (used by Graph::pull after confirming an upstream has truly moved).
Definition node.hpp:204
static Graph & graph() noexcept
Returns the process-wide singleton Graph this node belongs to.
Definition graph.inl:30
virtual std::shared_ptr< Node > retain_for_recompute() noexcept
Reactions are shared-owned and may cancel themselves while running.
Definition node.hpp:220
void notify_changed()
Called by a Source after its value has actually changed: bumps the version and colors all downstream ...
Definition graph.inl:117
Node(NodeKind kind) noexcept
Definition node.hpp:144
void detach_edge(Edge &edge) noexcept
Definition graph.inl:88
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
void for_each_observer(F &&f)
Iterate the observer list (used by Graph to color downstream nodes).
Definition node.hpp:234
void retire_() noexcept
Retire before derived members (including user captures) are destroyed.
Definition graph.inl:42
void mark_downstream_maybe_dirty()
Mark self and all reachable descendants as MaybeDirty.
Definition graph.inl:127
void attach_as_observer_of(Node &source, Edge &edge) noexcept
Definition graph.inl:64
virtual bool recompute()
Recompute hook for Derivation / Reaction nodes.
Definition node.hpp:216
NodeState state() const noexcept
Definition node.hpp:154
void set_state_(NodeState s) noexcept
Definition node.hpp:259
void clear_sources() noexcept
Drop every upstream edge.
Definition graph.inl:107
friend class Graph
Definition node.hpp:140
#define ARIA_ABI_API
Definition export.hpp:21
Definition computed.hpp:60
@ Computing
Definition node.hpp:106
@ Clean
Definition node.hpp:103
@ MaybeDirty
Definition node.hpp:104
Graph & graph_instance() noexcept
std::shared_ptr< FlushTraceFn > & flush_trace_hook_() noexcept
Definition diagnostics.hpp:95
ReactivePhase
Reactive flush phases.
Definition diagnostics.hpp:101
@ SkipClean
Definition diagnostics.hpp:105
@ RoundBegin
Definition diagnostics.hpp:103
@ FlushBegin
Definition diagnostics.hpp:102
@ Recomputed
Definition diagnostics.hpp:106
@ Pull
Definition diagnostics.hpp:104
@ RoundEnd
Definition diagnostics.hpp:107
@ FlushEnd
Definition diagnostics.hpp:108
void publish_trace_unchecked(const TraceEvent &event) noexcept
Publish an already-built event using one owning sink snapshot.
Definition diagnostics.hpp:293
bool has_trace_sink() noexcept
True iff a sink is currently installed.
Definition diagnostics.hpp:286
@ Reactive
Graph flush, push-color, pull-evaluate.
Definition diagnostics.hpp:67
void report_callback_failure(std::string_view category, std::exception_ptr exception, std::string_view message={}) noexcept
Report a callback failure.
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
Edge * prev_observer
Definition node.hpp:120
Edge * next_observer
Thread in the source's "observers" list (from the source's point of view: "these are the nodes watchi...
Definition node.hpp:119
Node * source
The upstream node being observed.
Definition node.hpp:114
Definition diagnostics.hpp:111