1use crate::pack::{push_u32, push_u32s, push_u64, read_u32, read_u32s, read_u64};
2use crate::types::Result as StoreResult;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use std::borrow::Cow;
5use std::cmp::Ordering;
6use std::collections::{BTreeSet, HashMap};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9pub enum Direction {
10 Out,
11 In,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum RemoveEdgeOutcome {
18 RemovedFromOverlay,
20 TombstonedBase,
25}
26
27const INSERT_BUFFER: usize = 32;
29
30#[derive(Debug, Default, Clone)]
33pub(crate) struct AdjList {
34 pub(crate) frozen: Vec<u32>,
35 pub(crate) delta: Vec<u32>,
36}
37
38impl AdjList {
39 fn contains(&self, id: u32) -> bool {
40 self.frozen.binary_search(&id).is_ok() || self.delta.contains(&id)
41 }
42
43 fn push(&mut self, id: u32) {
44 self.delta.push(id);
45 if self.delta.len() > INSERT_BUFFER {
46 self.flush();
47 }
48 }
49
50 fn flush(&mut self) {
51 if self.delta.is_empty() {
52 return;
53 }
54 if self.frozen.is_empty() {
55 self.delta.sort_unstable();
56 self.delta.dedup();
57 std::mem::swap(&mut self.frozen, &mut self.delta);
58 return;
59 }
60 self.frozen = merge_sorted_unique(&self.frozen, &self.delta);
61 self.delta.clear();
62 }
63
64 pub(crate) fn merged(&self) -> Cow<'_, [u32]> {
69 if self.delta.is_empty() {
70 Cow::Borrowed(&self.frozen)
71 } else {
72 Cow::Owned(merge_sorted_unique(&self.frozen, &self.delta))
73 }
74 }
75
76 fn remove(&mut self, id: u32) -> bool {
77 if let Ok(pos) = self.frozen.binary_search(&id) {
78 self.frozen.remove(pos);
79 return true;
80 }
81 if let Some(pos) = self.delta.iter().position(|&x| x == id) {
82 self.delta.swap_remove(pos);
83 return true;
84 }
85 false
86 }
87}
88
89impl Serialize for AdjList {
90 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
91 self.merged().serialize(serializer)
92 }
93}
94
95impl<'de> Deserialize<'de> for AdjList {
96 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
97 Ok(Self {
98 frozen: Vec::<u32>::deserialize(deserializer)?,
99 delta: Vec::new(),
100 })
101 }
102}
103
104fn merge_sorted_unique(frozen: &[u32], delta: &[u32]) -> Vec<u32> {
105 let mut extra: Vec<u32> = delta.to_vec();
106 extra.sort_unstable();
107 extra.dedup();
108 if frozen.is_empty() {
109 return extra;
110 }
111 if extra.is_empty() {
112 return frozen.to_vec();
113 }
114 let mut out = Vec::with_capacity(frozen.len() + extra.len());
115 let mut i = 0;
116 let mut j = 0;
117 while i < frozen.len() && j < extra.len() {
118 match frozen[i].cmp(&extra[j]) {
119 Ordering::Less => {
120 out.push(frozen[i]);
121 i += 1;
122 }
123 Ordering::Greater => {
124 out.push(extra[j]);
125 j += 1;
126 }
127 Ordering::Equal => {
128 out.push(frozen[i]);
129 i += 1;
130 j += 1;
131 }
132 }
133 }
134 out.extend_from_slice(&frozen[i..]);
135 out.extend_from_slice(&extra[j..]);
136 out
137}
138
139#[derive(Debug, Default, Clone, Serialize, Deserialize)]
140pub(crate) struct TypedAdjacency {
141 pub(crate) out: HashMap<u32, AdjList>,
142 pub(crate) inn: HashMap<u32, AdjList>,
143}
144
145#[derive(Debug, Default, Clone, Serialize, Deserialize)]
151pub struct Topology {
152 pub(crate) by_type: HashMap<u32, TypedAdjacency>,
153 edge_count: u64,
154 #[serde(skip)]
164 pub(crate) out_tombstones: HashMap<u32, HashMap<u32, BTreeSet<u32>>>,
165 #[serde(skip)]
168 pub(crate) in_tombstones: HashMap<u32, HashMap<u32, BTreeSet<u32>>>,
169}
170
171impl Topology {
172 pub fn new() -> Self {
173 Self::default()
174 }
175
176 pub fn add_edge(&mut self, etype: u32, src: u32, dst: u32) -> bool {
177 let adj = self.by_type.entry(etype).or_default();
178 let dsts = adj.out.entry(src).or_default();
179 if dsts.contains(dst) {
180 return false;
181 }
182 dsts.push(dst);
183 let srcs = adj.inn.entry(dst).or_default();
184 assert!(
185 !srcs.contains(src),
186 "invariant: inn must not contain src as neighbor of dst when out lacks dst"
187 );
188 srcs.push(src);
189 self.edge_count += 1;
190 true
191 }
192
193 pub fn neighbors(&self, etype: u32, dir: Direction, v: u32) -> Cow<'_, [u32]> {
194 match self.adj_list(etype, dir, v) {
195 None => Cow::Borrowed(&[]),
196 Some(n) => n.merged(),
197 }
198 }
199
200 pub fn degree(&self, etype: u32, dir: Direction, v: u32) -> usize {
201 self.neighbors(etype, dir, v).as_ref().len()
202 }
203
204 pub fn edge_count(&self) -> u64 {
205 self.edge_count
206 }
207
208 pub fn etypes(&self) -> impl Iterator<Item = u32> + '_ {
212 let mut ids: Vec<u32> = self.by_type.keys().copied().collect();
213 ids.sort_unstable();
214 ids.into_iter()
215 }
216
217 pub fn all_edges(&self) -> impl Iterator<Item = (u32, u32, u32)> + '_ {
223 self.by_type.iter().flat_map(|(&etype, adj)| {
224 adj.out.iter().flat_map(move |(&src, al)| {
225 al.merged()
226 .into_owned()
227 .into_iter()
228 .map(move |dst| (etype, src, dst))
229 })
230 })
231 }
232
233 pub fn remove_edge(&mut self, etype: u32, src: u32, dst: u32) -> RemoveEdgeOutcome {
242 let found_in_overlay = (|| {
243 let adj = self.by_type.get_mut(&etype)?;
244 let dsts = adj.out.get_mut(&src)?;
245 if !dsts.remove(dst) {
246 return None;
247 }
248 let srcs = adj
249 .inn
250 .get_mut(&dst)
251 .expect("invariant: inn bucket must exist when out contains dst");
252 assert!(
253 srcs.remove(src),
254 "invariant: inn must contain src when out contained dst"
255 );
256 self.edge_count -= 1;
257 Some(())
258 })()
259 .is_some();
260
261 if !found_in_overlay {
262 self.out_tombstones
266 .entry(etype)
267 .or_default()
268 .entry(src)
269 .or_default()
270 .insert(dst);
271 self.in_tombstones
272 .entry(etype)
273 .or_default()
274 .entry(dst)
275 .or_default()
276 .insert(src);
277 RemoveEdgeOutcome::TombstonedBase
278 } else {
279 RemoveEdgeOutcome::RemovedFromOverlay
280 }
281 }
282
283 pub fn out_tombstones_for(&self, etype: u32, src: u32) -> Option<&BTreeSet<u32>> {
287 self.out_tombstones.get(&etype)?.get(&src)
288 }
289
290 pub fn in_tombstones_for(&self, etype: u32, dst: u32) -> Option<&BTreeSet<u32>> {
292 self.in_tombstones.get(&etype)?.get(&dst)
293 }
294
295 fn adj_list(&self, etype: u32, dir: Direction, v: u32) -> Option<&AdjList> {
296 self.by_type.get(&etype).and_then(|adj| match dir {
297 Direction::Out => adj.out.get(&v),
298 Direction::In => adj.inn.get(&v),
299 })
300 }
301
302 pub(crate) fn pack(&self, out: &mut Vec<u8>) {
306 let mut etypes: Vec<u32> = self.by_type.keys().copied().collect();
307 etypes.sort_unstable();
308 push_u32(out, etypes.len() as u32);
309 for et in etypes {
310 push_u32(out, et);
311 let adj = &self.by_type[&et];
312 pack_adj_map(out, &adj.out);
313 pack_adj_map(out, &adj.inn);
314 }
315 push_u64(out, self.edge_count);
316 }
317
318 pub(crate) fn unpack(src: &[u8]) -> StoreResult<(Self, usize)> {
319 let mut pos = 0usize;
320 let n_etypes = read_u32(src, &mut pos)? as usize;
321 let mut by_type = HashMap::with_capacity(n_etypes);
322 for _ in 0..n_etypes {
323 let et = read_u32(src, &mut pos)?;
324 let out = unpack_adj_map(src, &mut pos)?;
325 let inn = unpack_adj_map(src, &mut pos)?;
326 by_type.insert(et, TypedAdjacency { out, inn });
327 }
328 let edge_count = read_u64(src, &mut pos)?;
329 Ok((
330 Self {
331 by_type,
332 edge_count,
333 out_tombstones: HashMap::new(),
334 in_tombstones: HashMap::new(),
335 },
336 pos,
337 ))
338 }
339}
340
341fn pack_adj_map(out: &mut Vec<u8>, map: &HashMap<u32, AdjList>) {
342 let mut verts: Vec<u32> = map.keys().copied().collect();
343 verts.sort_unstable();
344 push_u32(out, verts.len() as u32);
345 for v in verts {
346 push_u32(out, v);
347 let list = &map[&v];
348 push_u32s(out, list.merged().as_ref());
349 }
350}
351
352fn unpack_adj_map(src: &[u8], pos: &mut usize) -> StoreResult<HashMap<u32, AdjList>> {
353 let n = read_u32(src, pos)? as usize;
354 let mut map = HashMap::with_capacity(n);
355 for _ in 0..n {
356 let v = read_u32(src, pos)?;
357 let frozen = read_u32s(src, pos)?;
358 map.insert(
359 v,
360 AdjList {
361 frozen,
362 delta: Vec::new(),
363 },
364 );
365 }
366 Ok(map)
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn edges_are_typed_directed_sorted_deduped() {
375 let mut t = Topology::new();
376 assert!(t.add_edge(0, 5, 9));
377 assert!(t.add_edge(0, 5, 3));
378 assert!(!t.add_edge(0, 5, 9)); assert!(t.add_edge(1, 5, 9)); assert_eq!(t.neighbors(0, Direction::Out, 5).as_ref(), &[3, 9]); assert_eq!(t.neighbors(0, Direction::In, 9).as_ref(), &[5]);
382 assert_eq!(t.neighbors(0, Direction::Out, 999).as_ref(), &[] as &[u32]);
383 assert_eq!(t.degree(0, Direction::Out, 5), 2);
384 assert_eq!(t.edge_count(), 3);
385 }
386
387 #[test]
388 fn remove_edge_updates_both_sides_and_count() {
389 let mut t = Topology::new();
390 t.add_edge(0, 1, 2);
391 t.add_edge(0, 1, 3);
392 assert_eq!(
393 t.remove_edge(0, 1, 2),
394 RemoveEdgeOutcome::RemovedFromOverlay
395 );
396 assert_eq!(t.remove_edge(0, 1, 2), RemoveEdgeOutcome::TombstonedBase); assert_eq!(t.remove_edge(9, 1, 2), RemoveEdgeOutcome::TombstonedBase); assert_eq!(t.neighbors(0, Direction::Out, 1).as_ref(), &[3]);
399 assert_eq!(t.neighbors(0, Direction::In, 2).as_ref(), &[] as &[u32]);
400 assert_eq!(t.edge_count(), 1);
401 assert!(t.add_edge(0, 1, 2));
403 assert_eq!(t.edge_count(), 2);
404 }
405
406 #[test]
407 fn etypes_empty_multiple_and_sorted() {
408 let empty = Topology::new();
409 assert_eq!(empty.etypes().collect::<Vec<_>>(), Vec::<u32>::new());
410
411 let mut t = Topology::new();
412 t.add_edge(3, 0, 1);
413 t.add_edge(1, 0, 1);
414 t.add_edge(3, 1, 2); t.add_edge(2, 0, 2);
416 assert_eq!(t.etypes().collect::<Vec<_>>(), vec![1, 2, 3]);
417 }
418
419 #[test]
420 fn insert_buffer_defers_sort_until_threshold_then_neighbors_sorted_unique() {
421 let mut t = Topology::new();
422 for dst in (0u32..32).rev() {
424 assert!(t.add_edge(0, 0, dst));
425 let nbrs = t.neighbors(0, Direction::Out, 0);
426 assert!(
427 matches!(nbrs, Cow::Owned(_)),
428 "delta still dirty at {} edges; neighbors must take the owned merge path",
429 32 - dst
430 );
431 assert!(
432 nbrs.windows(2).all(|w| w[0] < w[1]),
433 "dirty merge must be sorted unique, got {nbrs:?}"
434 );
435 assert_eq!(nbrs.len(), (32 - dst) as usize);
436 let n = t.adj_list(0, Direction::Out, 0).unwrap();
437 assert!(
438 n.frozen.is_empty(),
439 "must not flush frozen before threshold"
440 );
441 assert_eq!(n.delta.len(), (32 - dst) as usize);
442 }
443 assert_eq!(t.degree(0, Direction::Out, 0), 32);
444 assert_eq!(t.neighbors(0, Direction::In, 31).as_ref(), &[0]);
445
446 assert!(t.add_edge(0, 0, 32)); let nbrs = t.neighbors(0, Direction::Out, 0);
448 assert!(
449 matches!(nbrs, Cow::Borrowed(_)),
450 "after threshold flush, scan path must borrow the frozen block"
451 );
452 let expected: Vec<u32> = (0..33).collect();
453 assert_eq!(nbrs.as_ref(), expected.as_slice());
454 let n = t.adj_list(0, Direction::Out, 0).unwrap();
455 assert!(n.delta.is_empty());
456 assert_eq!(n.frozen, expected);
457 assert_eq!(t.edge_count(), 33);
458 assert_eq!(t.neighbors(0, Direction::In, 32).as_ref(), &[0]);
459 assert!(!t.add_edge(0, 0, 7));
460 }
461
462 #[test]
463 fn remove_edge_from_delta_and_from_frozen() {
464 let mut t = Topology::new();
465 for dst in 0u32..10 {
466 assert!(t.add_edge(0, 1, dst));
467 }
468 assert!(matches!(t.neighbors(0, Direction::Out, 1), Cow::Owned(_)));
469 assert_eq!(
470 t.remove_edge(0, 1, 7),
471 RemoveEdgeOutcome::RemovedFromOverlay
472 );
473 assert_eq!(t.remove_edge(0, 1, 7), RemoveEdgeOutcome::TombstonedBase);
474 assert_eq!(t.neighbors(0, Direction::In, 7).as_ref(), &[] as &[u32]);
475 assert_eq!(
476 t.neighbors(0, Direction::Out, 1).as_ref(),
477 &[0, 1, 2, 3, 4, 5, 6, 8, 9]
478 );
479 assert_eq!(t.edge_count(), 9);
480 assert!(t.add_edge(0, 1, 7));
481 assert_eq!(t.edge_count(), 10);
482
483 let mut t = Topology::new();
484 for dst in 0u32..33 {
485 assert!(t.add_edge(0, 1, dst));
486 }
487 assert!(matches!(
488 t.neighbors(0, Direction::Out, 1),
489 Cow::Borrowed(_)
490 ));
491 assert_eq!(
492 t.remove_edge(0, 1, 0),
493 RemoveEdgeOutcome::RemovedFromOverlay
494 );
495 assert_eq!(
496 t.remove_edge(0, 1, 32),
497 RemoveEdgeOutcome::RemovedFromOverlay
498 );
499 assert_eq!(t.edge_count(), 31);
500 assert_eq!(t.neighbors(0, Direction::In, 0).as_ref(), &[] as &[u32]);
501 assert_eq!(t.neighbors(0, Direction::In, 16).as_ref(), &[1]);
502 let expected: Vec<u32> = (1..32).collect();
503 assert_eq!(
504 t.neighbors(0, Direction::Out, 1).as_ref(),
505 expected.as_slice()
506 );
507 assert!(t.add_edge(0, 1, 0));
508 assert_eq!(t.edge_count(), 32);
509 }
510
511 #[test]
512 fn serde_wire_is_hashmap_of_hashmap_of_vec() {
513 #[derive(Serialize, Deserialize, PartialEq, Debug)]
514 struct WireAdj {
515 out: HashMap<u32, Vec<u32>>,
516 inn: HashMap<u32, Vec<u32>>,
517 }
518 #[derive(Serialize, Deserialize, PartialEq, Debug)]
519 struct Wire {
520 by_type: HashMap<u32, WireAdj>,
521 edge_count: u64,
522 }
523
524 let mut by_type = HashMap::new();
525 by_type.insert(
526 0,
527 WireAdj {
528 out: HashMap::from([(5, vec![3, 9])]),
529 inn: HashMap::from([(3, vec![5]), (9, vec![5])]),
530 },
531 );
532 let wire = Wire {
533 by_type,
534 edge_count: 2,
535 };
536 let encoded = bincode::serialize(&wire).unwrap();
537 let t: Topology = bincode::deserialize(&encoded).unwrap();
538 assert_eq!(t.neighbors(0, Direction::Out, 5).as_ref(), &[3, 9]);
539 assert_eq!(t.neighbors(0, Direction::In, 3).as_ref(), &[5]);
540 assert_eq!(t.neighbors(0, Direction::In, 9).as_ref(), &[5]);
541 assert_eq!(t.edge_count(), 2);
542 assert!(matches!(
543 t.neighbors(0, Direction::Out, 5),
544 Cow::Borrowed(_)
545 ));
546
547 let roundtrip: Wire = bincode::deserialize(&bincode::serialize(&t).unwrap()).unwrap();
548 assert_eq!(roundtrip.edge_count, 2);
549 assert_eq!(roundtrip.by_type[&0].out[&5], vec![3, 9]);
550 assert_eq!(roundtrip.by_type[&0].inn[&3], vec![5]);
551 assert_eq!(roundtrip.by_type[&0].inn[&9], vec![5]);
552
553 let mut dirty = Topology::new();
555 for dst in (0u32..10).rev() {
556 dirty.add_edge(1, 0, dst);
557 }
558 assert!(matches!(
559 dirty.neighbors(1, Direction::Out, 0),
560 Cow::Owned(_)
561 ));
562 let dirty_wire: Wire = bincode::deserialize(&bincode::serialize(&dirty).unwrap()).unwrap();
563 assert_eq!(dirty_wire.edge_count, 10);
564 assert_eq!(
565 dirty_wire.by_type[&1].out[&0],
566 (0..10).collect::<Vec<u32>>()
567 );
568 for dst in 0..10 {
569 assert_eq!(dirty_wire.by_type[&1].inn[&dst], vec![0]);
570 }
571 }
572
573 #[test]
574 fn pack_roundtrip_merges_delta_and_restores_frozen() {
575 let mut t = Topology::new();
576 for dst in (0u32..10).rev() {
577 t.add_edge(2, 1, dst);
578 }
579 t.add_edge(0, 5, 9);
580 assert!(matches!(t.neighbors(2, Direction::Out, 1), Cow::Owned(_)));
581 let mut buf = Vec::new();
582 t.pack(&mut buf);
583 let (back, consumed) = Topology::unpack(&buf).unwrap();
584 assert_eq!(consumed, buf.len());
585 assert_eq!(back.edge_count(), 11);
586 let expected: Vec<u32> = (0..10).collect();
587 assert_eq!(
588 back.neighbors(2, Direction::Out, 1).as_ref(),
589 expected.as_slice()
590 );
591 assert!(matches!(
592 back.neighbors(2, Direction::Out, 1),
593 Cow::Borrowed(_)
594 ));
595 assert_eq!(back.neighbors(0, Direction::Out, 5).as_ref(), &[9]);
596 assert_eq!(back.neighbors(0, Direction::In, 9).as_ref(), &[5]);
597 assert_eq!(back.etypes().collect::<Vec<_>>(), vec![0, 2]);
598 }
599}