invalidation
invalidation provides dependency-aware invalidation primitives for
incremental systems.
It is a small #![no_std] crate for situations where upstream changes must
flow through a dependency graph and downstream work should be processed in a
clear, explicit order.
The crate is centered on a small set of building blocks:
ChannelandChannelSetfor named invalidation domainsInvalidationGraphfor dependency edges and cycle handlingInvalidationSetfor accumulated invalidated keysEagerPolicyandLazyPolicyfor propagation strategyInvalidationTrackerfor the combined convenience APIDrainBuilderand deterministic drain helpers for ordered processing
It intentionally does not own recomputation, caching, or scheduling. Those remain in your application.
Quick Start
use ;
const LAYOUT: Channel = new;
let mut tracker = new;
tracker.add_dependency.unwrap;
tracker.add_dependency.unwrap;
tracker.mark_with;
let ordered: = tracker.drain_sorted.collect;
assert_eq!;
Concepts
- key: a node identifier in your system
- channel: an invalidation domain such as layout or paint
- dependency:
A depends on BmeansBmust be processed beforeA - invalidated root: a key you explicitly mark invalidated
- affected key: an invalidated root or one of its transitive dependents
- drain: consume invalidated work in dependency order and clear it
Choosing The Main API
- Use
InvalidationTrackerfor the most direct “just give me the pieces together” workflow. - Use
InvalidationGraphplusInvalidationSetseparately if your embedder already owns state and only wants the primitives. - Use
DrainBuilderwhen you need deterministic ordering, targeted drains, scratch reuse, or tracing. - Use
intern::Internerwhen your natural keys are strings or other non-Copyvalues.
Eager vs Lazy
Two workflows are intentionally first-class:
EagerPolicy: propagate immediately at mark time, then usedrain_sorted.LazyPolicy: mark only roots at change time, then expand withdrain_affected_sortedorDrainBuilder::affected.
use ;
const LAYOUT: Channel = new;
let mut eager = new;
eager.add_dependency.unwrap;
eager.add_dependency.unwrap;
eager.mark_with;
assert!;
let mut lazy = new;
lazy.add_dependency.unwrap;
lazy.add_dependency.unwrap;
lazy.mark_with;
assert!;
let ordered: = lazy.drain_affected_sorted.collect;
assert_eq!;
Deterministic And Targeted Drains
When ties must be stable, or you only want to process part of the invalidated
region, use DrainBuilder:
use ;
const LAYOUT: Channel = new;
let mut tracker = new;
tracker.add_dependency.unwrap;
tracker.add_dependency.unwrap;
tracker.add_dependency.unwrap;
tracker.add_dependency.unwrap;
tracker.mark;
tracker.mark;
tracker.mark;
tracker.mark;
tracker.mark;
let focused: = tracker
.drain
.within_dependencies_of
.deterministic
.collect;
assert_eq!;
assert!;
assert!;
Non-Copy Keys
invalidation keeps its core APIs keyed by K: Copy so hot paths stay
predictable. If your natural keys are strings or other owned values, intern
them first:
use ;
const STYLE: Channel = new;
;
let mut ids = new;
let stylesheet = ids.intern;
let button = ids.intern;
let mut tracker = new;
tracker.add_dependency.unwrap;
tracker.mark_with;
let affected: = tracker.drain_affected_sorted.collect;
assert_eq!;
Gotchas
add_dependency(a, b, ...)meansadepends onb, not the reverse.LazyPolicyanddrain_sortedare usually the wrong pair.- Deterministic dense drains assume a compact key space; use
Internerwhen keys are sparse or structured. - If cycles are allowed, topological drains can stall.
Minimum Supported Rust Version (MSRV)
This version of invalidation has been verified to compile with Rust
1.92 and later.
Future versions might increase the Rust version requirement. That is not treated as a breaking change and may happen in minor or patch releases.