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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Copyright 2026 the Invalidation Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Invalidation: generic primitives for dependency-aware invalidation.
//!
//! This crate helps incremental systems track what changed, propagate that
//! invalidation through dependency edges, and drain the affected work in a
//! clear order.
//!
//! Most applications should start with [`InvalidationTracker`]. It is the
//! coordinator that owns dependency edges, invalidation state, channel
//! cascades, cross-channel edges, and drain entry points.
//!
//! The lower-level pieces are available when an embedder already owns part of
//! that coordination:
//!
//! - **Tracker** ([`InvalidationTracker`]): The common high-level API for
//! graph mutation, marking, propagation, cascades, cross-channel edges, and
//! draining.
//! - **Channels** ([`Channel`], [`ChannelSet`]): Named domains for invalidation
//! tracking (for example, layout, paint, accessibility).
//! - **Dependency graphs** ([`InvalidationGraph`]): DAG of "A depends on B" edges,
//! with cycle detection and bidirectional traversal.
//! - **Invalidation sets** ([`InvalidationSet`]): Accumulated invalidated keys
//! per channel with
//! operation generation tracking for stale-observation detection.
//! - **Propagation policies** ([`PropagationPolicy`], [`EagerPolicy`], [`LazyPolicy`]):
//! Pluggable strategies for how invalidation spreads through the graph.
//! - **Topological drain** ([`DrainSorted`]): Kahn's algorithm to yield invalidated
//! keys in dependency order.
//! - **Channel cascades** ([`ChannelCascade`]): DAG of "invalidation on channel A
//! also marks channel B" rules, with precomputed transitive closure.
//! - **Cross-channel edges** ([`CrossChannelEdges`]): Sparse `(key, channel) →
//! (key, channel)` edges for cross-key cross-channel dependencies.
//! - **Scratch buffers** ([`TraversalScratch`]): Reusable traversal state for
//! tight loops to avoid repeated allocations.
//!
//! This crate owns invalidation primitives. It explicitly does not own
//! recomputation, caching, or scheduling policy.
//!
//! ## Quick Start
//!
//! ```rust
//! use invalidation::{Channel, InvalidationTracker, EagerPolicy};
//!
//! const LAYOUT: Channel = Channel::new(0);
//! const PAINT: Channel = Channel::new(1);
//!
//! let mut tracker = InvalidationTracker::<u32>::new();
//! // `u32` is fine for compact 0-based IDs. Sparse/external IDs should be
//! // interned first so dense storage grows with node count, not key magnitude.
//!
//! // Build dependency graph: 3 depends on 2, 2 depends on 1
//! tracker.add_dependency(2, 1, LAYOUT).unwrap();
//! tracker.add_dependency(3, 2, LAYOUT).unwrap();
//!
//! // Mark with eager propagation (marks 1, 2, 3)
//! tracker.mark_with(1, LAYOUT, &EagerPolicy);
//!
//! // Or mark manually without propagation
//! tracker.mark(1, PAINT);
//!
//! // Drain in topological order: 1, 2, 3
//! for key in tracker.drain_sorted(LAYOUT) {
//! let _ = key;
//! }
//! ```
//!
//! ## Choosing An API
//!
//! - Use [`InvalidationTracker`] when one coordinator should own dependency
//! edges, invalidation state, channel cascades, and cross-channel edges.
//! - Use [`InvalidationGraph`] plus [`InvalidationSet`] separately if your
//! embedder already owns propagation or scheduling policy and just wants the
//! primitives.
//! - Use [`DrainBuilder`] when you need deterministic drains, targeted drains,
//! scratch reuse, or tracing.
//! - Use [`intern::Interner`] when your natural keys are not already compact
//! `Copy` identifiers.
//!
//! ## Marking Work
//!
//! - Use [`InvalidationTracker::mark_with`] for normal update workflows. It
//! applies the chosen [`PropagationPolicy`], channel cascades, and
//! cross-channel edges until the invalidation closure is complete.
//! - Use [`InvalidationTracker::mark`] only when you intentionally want a
//! direct mark without same-channel graph traversal or cross-channel edge
//! traversal. It still applies same-key channel cascades.
//!
//! ## Using Components Separately
//!
//! While [`InvalidationTracker`] coordinates the common workflow, you can also
//! use the lower-level types directly for more control:
//!
//! ```rust
//! use invalidation::{
//! Channel, CycleHandling, InvalidationGraph, InvalidationSet, EagerPolicy, PropagationPolicy,
//! };
//!
//! const LAYOUT: Channel = Channel::new(0);
//!
//! // Build the dependency graph
//! let mut graph = InvalidationGraph::<u32>::new();
//! // Dense storage expects compact key spaces; sparse/owned keys should be
//! // interned first.
//! graph.add_dependency(2, 1, LAYOUT, CycleHandling::Error).unwrap();
//! graph.add_dependency(3, 2, LAYOUT, CycleHandling::Error).unwrap();
//!
//! // Maintain invalidation state separately
//! let mut invalidated = InvalidationSet::new();
//! let eager = EagerPolicy;
//!
//! // Propagate invalidation marks
//! eager.propagate(1, LAYOUT, &graph, &mut invalidated);
//!
//! assert!(invalidated.is_invalidated(1, LAYOUT));
//! assert!(invalidated.is_invalidated(2, LAYOUT));
//! assert!(invalidated.is_invalidated(3, LAYOUT));
//! ```
//!
//! ## Propagation Policies
//!
//! The crate provides two built-in policies:
//!
//! - [`EagerPolicy`]: Immediately marks all transitive dependents when a key
//! is marked invalidated. Use this when you need to know the full invalidation set
//! immediately after marking. Use with [`InvalidationTracker::drain_sorted`].
//! - [`LazyPolicy`]: Only marks the key itself at mark time; no propagation
//! occurs. Use [`InvalidationTracker::drain_affected_sorted`] to expand and process
//! all affected keys at drain time.
//!
//! You can implement [`PropagationPolicy`] for custom strategies.
//!
//! ## Choosing a Drain Function
//!
//! - [`drain_sorted`] / [`InvalidationTracker::drain_sorted`]: Drain exactly the keys
//! that are currently marked invalidated, in topological order.
//! - [`drain_affected_sorted`] / [`InvalidationTracker::drain_affected_sorted`]:
//! Expand the invalidation set to include all transitive dependents before draining.
//! - [`DrainBuilder`]: Configure ordering, scope, scratch reuse, and tracing in
//! one place.
//!
//! ## Cycle Detection
//!
//! [`InvalidationGraph::add_dependency`] supports configurable cycle handling via
//! [`CycleHandling`]:
//!
//! - `DebugAssert` (default): Panic in debug builds, ignore in release.
//! - `Error`: Return `Err(CycleError)` if a cycle would be created.
//! - `Ignore`: Silently ignore the dependency.
//! - `Allow`: Skip cycle detection entirely.
//!
//! ## `no_std` Support
//!
//! This crate is `no_std` and uses `alloc`. It does not depend on `std`.
//!
//! ## Common Mistakes
//!
//! - `add_dependency(a, b, ...)` means `a` depends on `b`.
//! - [`InvalidationTracker::mark`] does not follow graph dependents or
//! cross-channel edges; [`InvalidationTracker::mark_with`] is the usual
//! full-closure marking API.
//! - [`LazyPolicy`] is usually paired with affected drains, not plain
//! [`drain_sorted`].
//! - Deterministic dense drains assume a compact key space; intern sparse or
//! structured keys first.
//! - If cycles are allowed, topological drains can stall.
extern crate alloc;
pub use ;
pub use ;
pub use CrossChannelEdges;
pub use ;
pub use ;
pub use ;
pub use InternId;
pub use ;
pub use TraversalScratch;
pub use InvalidationSet;
pub use ;
pub use InvalidationTracker;