graph_r/traverse.rs
1// Author: support@lituus.io
2// Copyright: lituus-io, all rights reserved
3//
4//! Zero-alloc traversal. [`LendingIterator`] is the crate's GAT seam: each
5//! step lends a view tied to the iterator's borrow, so implementations are
6//! free to reuse internal state per step without allocating — and future
7//! sources that materialize per-step stay possible behind the same trait.
8
9use crate::key::{EdgeType, NodeId, UrlKey};
10
11/// A GAT-based iterator whose items may borrow from the iterator itself.
12pub trait LendingIterator {
13 /// The lent item type.
14 type Item<'a>
15 where
16 Self: 'a;
17 /// Advance and lend the next item.
18 fn next(&mut self) -> Option<Self::Item<'_>>;
19}
20
21/// One outbound edge as seen from a snapshot.
22#[derive(Clone, Copy, Debug)]
23pub struct EdgeRef<'a> {
24 /// Destination node id in the current base generation, when resolved.
25 pub dst: Option<NodeId>,
26 /// Destination durable key (always present).
27 pub dst_key: UrlKey,
28 /// Destination canonical URL, when the destination is a known node with
29 /// a URL label (stubs have none).
30 pub dst_url: Option<&'a str>,
31 /// Edge type.
32 pub etype: EdgeType,
33 /// Raw edge flags (`format::base::eflags`).
34 pub flags: u8,
35 /// Weight in 1/65535 units.
36 pub weight: u16,
37}
38
39impl EdgeRef<'_> {
40 /// True when the edge was inferred rather than extracted from source.
41 #[must_use]
42 pub fn is_inferred(&self) -> bool {
43 self.flags & crate::format::base::eflags::INFERRED != 0
44 }
45}
46
47/// Lending iterator over a node's outbound edges. Constructed by
48/// [`crate::Snapshot::neighbors`]; each step resolves the destination lazily
49/// against the snapshot, allocating nothing.
50#[derive(Debug)]
51pub struct Neighbors<'s> {
52 pub(crate) inner: NeighborsInner<'s>,
53}
54
55#[derive(Debug)]
56pub(crate) enum NeighborsInner<'s> {
57 /// CSR run in the base file.
58 Base { snap: &'s crate::snapshot::Snapshot<'s>, edges: &'s [u8], idx: usize, end: usize },
59 /// Whole-set replacement living in the overlay.
60 Overlay {
61 snap: &'s crate::snapshot::Snapshot<'s>,
62 edges: &'s [crate::format::wal::OwnedEdge],
63 idx: usize,
64 },
65 /// The node has no edges.
66 Empty,
67}
68
69impl LendingIterator for Neighbors<'_> {
70 type Item<'a>
71 = EdgeRef<'a>
72 where
73 Self: 'a;
74
75 fn next(&mut self) -> Option<EdgeRef<'_>> {
76 match &mut self.inner {
77 NeighborsInner::Base { snap, edges, idx, end } => {
78 if idx >= end {
79 return None;
80 }
81 let rec = &edges[*idx * crate::format::base::EDGE_LEN
82 ..(*idx + 1) * crate::format::base::EDGE_LEN];
83 *idx += 1;
84 let dst = NodeId(crate::bytesio::get_u32(rec, 0));
85 let (dst_key, dst_url) = snap.node_identity(dst);
86 Some(EdgeRef {
87 dst: Some(dst),
88 dst_key,
89 dst_url,
90 etype: EdgeType::from_tag(rec[4]),
91 flags: rec[5],
92 weight: crate::bytesio::get_u16(rec, 6),
93 })
94 }
95 NeighborsInner::Overlay { snap, edges, idx } => {
96 let (dst_key, etype, flags, weight) = *edges.get(*idx)?;
97 *idx += 1;
98 let dst = snap.base_node_id(dst_key);
99 let dst_url = dst.and_then(|id| snap.node_identity(id).1);
100 Some(EdgeRef {
101 dst,
102 dst_key,
103 dst_url,
104 etype: EdgeType::from_tag(etype),
105 flags,
106 weight,
107 })
108 }
109 NeighborsInner::Empty => None,
110 }
111 }
112}
113
114/// Which edges a [`crate::Snapshot::walk`] follows.
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116pub enum Direction {
117 /// Outbound: from a node to what it points at (a column to the columns
118 /// it derives from — upstream lineage).
119 Out,
120 /// Inbound: from a node to what points at it (a column to the columns
121 /// computed from it — downstream impact).
122 In,
123}
124
125/// One node reached by a walk.
126#[derive(Clone, Copy, Debug, PartialEq, Eq)]
127pub struct Hop {
128 /// The node reached.
129 pub key: UrlKey,
130 /// The node it was reached from.
131 pub from: UrlKey,
132 /// The type of the edge crossed.
133 pub etype: EdgeType,
134 /// Hops from the start (1 for direct neighbours).
135 pub depth: u32,
136 /// Every edge on the path carries `eflags::EXACT` (an unchanged copy).
137 pub exact_chain: bool,
138}
139
140/// Reusable walk state (visited set and frontiers). Keep one per thread:
141/// once its buffers have grown, walks allocate nothing.
142#[derive(Debug, Default)]
143pub struct Walk {
144 seen: std::collections::HashSet<u64>,
145 frontier: Vec<(UrlKey, bool)>,
146 next: Vec<(UrlKey, bool)>,
147}
148
149impl Walk {
150 /// Empty walk state.
151 #[must_use]
152 pub fn new() -> Self {
153 Self::default()
154 }
155}
156
157impl crate::Snapshot<'_> {
158 /// Breadth-first walk from `from` along edges in `dir` whose type
159 /// `follow` accepts, at most `max_depth` hops. `visit` sees every node
160 /// reached once, nearest first, and returns whether to continue past it
161 /// (so "the nearest described ancestor" is a walk that stops at
162 /// described nodes).
163 pub fn walk(
164 &self,
165 from: UrlKey,
166 dir: Direction,
167 max_depth: u32,
168 follow: impl Fn(EdgeType) -> bool,
169 w: &mut Walk,
170 mut visit: impl FnMut(Hop) -> bool,
171 ) {
172 w.seen.clear();
173 w.frontier.clear();
174 w.seen.insert(from.0);
175 w.frontier.push((from, true));
176 let exact = crate::format::base::eflags::EXACT;
177 for depth in 1..=max_depth {
178 w.next.clear();
179 for k in 0..w.frontier.len() {
180 let (node, chain) = w.frontier[k];
181 let mut step = |key: UrlKey, etype: EdgeType, flags: u8, w: &mut Walk| {
182 if !follow(etype) || !w.seen.insert(key.0) {
183 return;
184 }
185 let hop = Hop {
186 key,
187 from: node,
188 etype,
189 depth,
190 exact_chain: chain && flags & exact != 0,
191 };
192 if visit(hop) {
193 w.next.push((key, hop.exact_chain));
194 }
195 };
196 match dir {
197 Direction::Out => {
198 let mut it = self.neighbors(node);
199 while let Some(e) = it.next() {
200 step(e.dst_key, e.etype, e.flags, w);
201 }
202 }
203 Direction::In => {
204 self.in_edges(node, |src, etype, flags, _| step(src, etype, flags, w));
205 }
206 }
207 }
208 if w.next.is_empty() {
209 break;
210 }
211 std::mem::swap(&mut w.frontier, &mut w.next);
212 }
213 }
214}