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 fn is_empty(&self) -> bool {
65 self.frozen.is_empty() && self.delta.is_empty()
66 }
67
68 pub(crate) fn merged(&self) -> Cow<'_, [u32]> {
73 if self.delta.is_empty() {
74 Cow::Borrowed(&self.frozen)
75 } else {
76 Cow::Owned(merge_sorted_unique(&self.frozen, &self.delta))
77 }
78 }
79
80 fn remove(&mut self, id: u32) -> bool {
81 if let Ok(pos) = self.frozen.binary_search(&id) {
82 self.frozen.remove(pos);
83 return true;
84 }
85 if let Some(pos) = self.delta.iter().position(|&x| x == id) {
86 self.delta.swap_remove(pos);
87 return true;
88 }
89 false
90 }
91}
92
93impl Serialize for AdjList {
94 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
95 self.merged().serialize(serializer)
96 }
97}
98
99impl<'de> Deserialize<'de> for AdjList {
100 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
101 Ok(Self {
102 frozen: Vec::<u32>::deserialize(deserializer)?,
103 delta: Vec::new(),
104 })
105 }
106}
107
108fn merge_sorted_unique(frozen: &[u32], delta: &[u32]) -> Vec<u32> {
109 let mut extra: Vec<u32> = delta.to_vec();
110 extra.sort_unstable();
111 extra.dedup();
112 if frozen.is_empty() {
113 return extra;
114 }
115 if extra.is_empty() {
116 return frozen.to_vec();
117 }
118 let mut out = Vec::with_capacity(frozen.len() + extra.len());
119 let mut i = 0;
120 let mut j = 0;
121 while i < frozen.len() && j < extra.len() {
122 match frozen[i].cmp(&extra[j]) {
123 Ordering::Less => {
124 out.push(frozen[i]);
125 i += 1;
126 }
127 Ordering::Greater => {
128 out.push(extra[j]);
129 j += 1;
130 }
131 Ordering::Equal => {
132 out.push(frozen[i]);
133 i += 1;
134 j += 1;
135 }
136 }
137 }
138 out.extend_from_slice(&frozen[i..]);
139 out.extend_from_slice(&extra[j..]);
140 out
141}
142
143#[derive(Debug, Default, Clone, Serialize, Deserialize)]
144pub(crate) struct TypedAdjacency {
145 pub(crate) out: HashMap<u32, AdjList>,
146 pub(crate) inn: HashMap<u32, AdjList>,
147}
148
149#[derive(Debug, Default, Clone, Serialize, Deserialize)]
155pub struct Topology {
156 pub(crate) by_type: HashMap<u32, TypedAdjacency>,
157 edge_count: u64,
158 #[serde(skip)]
168 pub(crate) out_tombstones: HashMap<u32, HashMap<u32, BTreeSet<u32>>>,
169 #[serde(skip)]
172 pub(crate) in_tombstones: HashMap<u32, HashMap<u32, BTreeSet<u32>>>,
173}
174
175impl Topology {
176 pub fn new() -> Self {
177 Self::default()
178 }
179
180 pub fn add_edge(&mut self, etype: u32, src: u32, dst: u32) -> bool {
181 let adj = self.by_type.entry(etype).or_default();
182 let dsts = adj.out.entry(src).or_default();
183 if dsts.contains(dst) {
184 return false;
185 }
186 dsts.push(dst);
187 let srcs = adj.inn.entry(dst).or_default();
188 assert!(
189 !srcs.contains(src),
190 "invariant: inn must not contain src as neighbor of dst when out lacks dst"
191 );
192 srcs.push(src);
193 self.edge_count += 1;
194 true
195 }
196
197 pub fn neighbors(&self, etype: u32, dir: Direction, v: u32) -> Cow<'_, [u32]> {
198 match self.adj_list(etype, dir, v) {
199 None => Cow::Borrowed(&[]),
200 Some(n) => n.merged(),
201 }
202 }
203
204 pub fn degree(&self, etype: u32, dir: Direction, v: u32) -> usize {
205 self.neighbors(etype, dir, v).as_ref().len()
206 }
207
208 pub(crate) fn adj_is_empty(&self, etype: u32, dir: Direction, v: u32) -> bool {
213 self.adj_list(etype, dir, v).is_none_or(AdjList::is_empty)
214 }
215
216 pub(crate) fn tombstones_are_empty(&self, etype: u32, dir: Direction, v: u32) -> bool {
218 let set = match dir {
219 Direction::Out => self.out_tombstones_for(etype, v),
220 Direction::In => self.in_tombstones_for(etype, v),
221 };
222 set.is_none_or(|s| s.is_empty())
223 }
224
225 pub fn edge_count(&self) -> u64 {
226 self.edge_count
227 }
228
229 pub fn tombstone_count(&self) -> u64 {
235 self.out_tombstones
236 .values()
237 .flat_map(|m| m.values())
238 .map(|s| s.len() as u64)
239 .sum()
240 }
241
242 pub fn etypes(&self) -> impl Iterator<Item = u32> + '_ {
246 let mut ids: Vec<u32> = self.by_type.keys().copied().collect();
247 ids.sort_unstable();
248 ids.into_iter()
249 }
250
251 pub fn all_edges(&self) -> impl Iterator<Item = (u32, u32, u32)> + '_ {
257 self.by_type.iter().flat_map(|(&etype, adj)| {
258 adj.out.iter().flat_map(move |(&src, al)| {
259 al.merged()
260 .into_owned()
261 .into_iter()
262 .map(move |dst| (etype, src, dst))
263 })
264 })
265 }
266
267 pub fn remove_edge(&mut self, etype: u32, src: u32, dst: u32) -> RemoveEdgeOutcome {
276 let found_in_overlay = (|| {
277 let adj = self.by_type.get_mut(&etype)?;
278 let dsts = adj.out.get_mut(&src)?;
279 if !dsts.remove(dst) {
280 return None;
281 }
282 let srcs = adj
283 .inn
284 .get_mut(&dst)
285 .expect("invariant: inn bucket must exist when out contains dst");
286 assert!(
287 srcs.remove(src),
288 "invariant: inn must contain src when out contained dst"
289 );
290 self.edge_count -= 1;
291 Some(())
292 })()
293 .is_some();
294
295 if !found_in_overlay {
296 self.out_tombstones
300 .entry(etype)
301 .or_default()
302 .entry(src)
303 .or_default()
304 .insert(dst);
305 self.in_tombstones
306 .entry(etype)
307 .or_default()
308 .entry(dst)
309 .or_default()
310 .insert(src);
311 RemoveEdgeOutcome::TombstonedBase
312 } else {
313 RemoveEdgeOutcome::RemovedFromOverlay
314 }
315 }
316
317 pub fn out_tombstones_for(&self, etype: u32, src: u32) -> Option<&BTreeSet<u32>> {
321 self.out_tombstones.get(&etype)?.get(&src)
322 }
323
324 pub fn in_tombstones_for(&self, etype: u32, dst: u32) -> Option<&BTreeSet<u32>> {
326 self.in_tombstones.get(&etype)?.get(&dst)
327 }
328
329 fn adj_list(&self, etype: u32, dir: Direction, v: u32) -> Option<&AdjList> {
330 self.by_type.get(&etype).and_then(|adj| match dir {
331 Direction::Out => adj.out.get(&v),
332 Direction::In => adj.inn.get(&v),
333 })
334 }
335
336 pub(crate) fn pack(&self, out: &mut Vec<u8>) {
340 let mut etypes: Vec<u32> = self.by_type.keys().copied().collect();
341 etypes.sort_unstable();
342 push_u32(out, etypes.len() as u32);
343 for et in etypes {
344 push_u32(out, et);
345 let adj = &self.by_type[&et];
346 pack_adj_map(out, &adj.out);
347 pack_adj_map(out, &adj.inn);
348 }
349 push_u64(out, self.edge_count);
350 }
351
352 pub(crate) fn unpack(src: &[u8]) -> StoreResult<(Self, usize)> {
353 let mut pos = 0usize;
354 let n_etypes = read_u32(src, &mut pos)? as usize;
355 let mut by_type = HashMap::with_capacity(n_etypes);
356 for _ in 0..n_etypes {
357 let et = read_u32(src, &mut pos)?;
358 let out = unpack_adj_map(src, &mut pos)?;
359 let inn = unpack_adj_map(src, &mut pos)?;
360 by_type.insert(et, TypedAdjacency { out, inn });
361 }
362 let edge_count = read_u64(src, &mut pos)?;
363 Ok((
364 Self {
365 by_type,
366 edge_count,
367 out_tombstones: HashMap::new(),
368 in_tombstones: HashMap::new(),
369 },
370 pos,
371 ))
372 }
373}
374
375fn pack_adj_map(out: &mut Vec<u8>, map: &HashMap<u32, AdjList>) {
376 let mut verts: Vec<u32> = map.keys().copied().collect();
377 verts.sort_unstable();
378 push_u32(out, verts.len() as u32);
379 for v in verts {
380 push_u32(out, v);
381 let list = &map[&v];
382 push_u32s(out, list.merged().as_ref());
383 }
384}
385
386fn unpack_adj_map(src: &[u8], pos: &mut usize) -> StoreResult<HashMap<u32, AdjList>> {
387 let n = read_u32(src, pos)? as usize;
388 let mut map = HashMap::with_capacity(n);
389 for _ in 0..n {
390 let v = read_u32(src, pos)?;
391 let frozen = read_u32s(src, pos)?;
392 map.insert(
393 v,
394 AdjList {
395 frozen,
396 delta: Vec::new(),
397 },
398 );
399 }
400 Ok(map)
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 #[test]
408 fn edges_are_typed_directed_sorted_deduped() {
409 let mut t = Topology::new();
410 assert!(t.add_edge(0, 5, 9));
411 assert!(t.add_edge(0, 5, 3));
412 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]);
416 assert_eq!(t.neighbors(0, Direction::Out, 999).as_ref(), &[] as &[u32]);
417 assert_eq!(t.degree(0, Direction::Out, 5), 2);
418 assert_eq!(t.edge_count(), 3);
419 }
420
421 #[test]
422 fn remove_edge_updates_both_sides_and_count() {
423 let mut t = Topology::new();
424 t.add_edge(0, 1, 2);
425 t.add_edge(0, 1, 3);
426 assert_eq!(
427 t.remove_edge(0, 1, 2),
428 RemoveEdgeOutcome::RemovedFromOverlay
429 );
430 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]);
433 assert_eq!(t.neighbors(0, Direction::In, 2).as_ref(), &[] as &[u32]);
434 assert_eq!(t.edge_count(), 1);
435 assert!(t.add_edge(0, 1, 2));
437 assert_eq!(t.edge_count(), 2);
438 }
439
440 #[test]
441 fn etypes_empty_multiple_and_sorted() {
442 let empty = Topology::new();
443 assert_eq!(empty.etypes().collect::<Vec<_>>(), Vec::<u32>::new());
444
445 let mut t = Topology::new();
446 t.add_edge(3, 0, 1);
447 t.add_edge(1, 0, 1);
448 t.add_edge(3, 1, 2); t.add_edge(2, 0, 2);
450 assert_eq!(t.etypes().collect::<Vec<_>>(), vec![1, 2, 3]);
451 }
452
453 #[test]
454 fn insert_buffer_defers_sort_until_threshold_then_neighbors_sorted_unique() {
455 let mut t = Topology::new();
456 for dst in (0u32..32).rev() {
458 assert!(t.add_edge(0, 0, dst));
459 let nbrs = t.neighbors(0, Direction::Out, 0);
460 assert!(
461 matches!(nbrs, Cow::Owned(_)),
462 "delta still dirty at {} edges; neighbors must take the owned merge path",
463 32 - dst
464 );
465 assert!(
466 nbrs.windows(2).all(|w| w[0] < w[1]),
467 "dirty merge must be sorted unique, got {nbrs:?}"
468 );
469 assert_eq!(nbrs.len(), (32 - dst) as usize);
470 let n = t.adj_list(0, Direction::Out, 0).unwrap();
471 assert!(
472 n.frozen.is_empty(),
473 "must not flush frozen before threshold"
474 );
475 assert_eq!(n.delta.len(), (32 - dst) as usize);
476 }
477 assert_eq!(t.degree(0, Direction::Out, 0), 32);
478 assert_eq!(t.neighbors(0, Direction::In, 31).as_ref(), &[0]);
479
480 assert!(t.add_edge(0, 0, 32)); let nbrs = t.neighbors(0, Direction::Out, 0);
482 assert!(
483 matches!(nbrs, Cow::Borrowed(_)),
484 "after threshold flush, scan path must borrow the frozen block"
485 );
486 let expected: Vec<u32> = (0..33).collect();
487 assert_eq!(nbrs.as_ref(), expected.as_slice());
488 let n = t.adj_list(0, Direction::Out, 0).unwrap();
489 assert!(n.delta.is_empty());
490 assert_eq!(n.frozen, expected);
491 assert_eq!(t.edge_count(), 33);
492 assert_eq!(t.neighbors(0, Direction::In, 32).as_ref(), &[0]);
493 assert!(!t.add_edge(0, 0, 7));
494 }
495
496 #[test]
497 fn remove_edge_from_delta_and_from_frozen() {
498 let mut t = Topology::new();
499 for dst in 0u32..10 {
500 assert!(t.add_edge(0, 1, dst));
501 }
502 assert!(matches!(t.neighbors(0, Direction::Out, 1), Cow::Owned(_)));
503 assert_eq!(
504 t.remove_edge(0, 1, 7),
505 RemoveEdgeOutcome::RemovedFromOverlay
506 );
507 assert_eq!(t.remove_edge(0, 1, 7), RemoveEdgeOutcome::TombstonedBase);
508 assert_eq!(t.neighbors(0, Direction::In, 7).as_ref(), &[] as &[u32]);
509 assert_eq!(
510 t.neighbors(0, Direction::Out, 1).as_ref(),
511 &[0, 1, 2, 3, 4, 5, 6, 8, 9]
512 );
513 assert_eq!(t.edge_count(), 9);
514 assert!(t.add_edge(0, 1, 7));
515 assert_eq!(t.edge_count(), 10);
516
517 let mut t = Topology::new();
518 for dst in 0u32..33 {
519 assert!(t.add_edge(0, 1, dst));
520 }
521 assert!(matches!(
522 t.neighbors(0, Direction::Out, 1),
523 Cow::Borrowed(_)
524 ));
525 assert_eq!(
526 t.remove_edge(0, 1, 0),
527 RemoveEdgeOutcome::RemovedFromOverlay
528 );
529 assert_eq!(
530 t.remove_edge(0, 1, 32),
531 RemoveEdgeOutcome::RemovedFromOverlay
532 );
533 assert_eq!(t.edge_count(), 31);
534 assert_eq!(t.neighbors(0, Direction::In, 0).as_ref(), &[] as &[u32]);
535 assert_eq!(t.neighbors(0, Direction::In, 16).as_ref(), &[1]);
536 let expected: Vec<u32> = (1..32).collect();
537 assert_eq!(
538 t.neighbors(0, Direction::Out, 1).as_ref(),
539 expected.as_slice()
540 );
541 assert!(t.add_edge(0, 1, 0));
542 assert_eq!(t.edge_count(), 32);
543 }
544
545 #[test]
546 fn serde_wire_is_hashmap_of_hashmap_of_vec() {
547 #[derive(Serialize, Deserialize, PartialEq, Debug)]
548 struct WireAdj {
549 out: HashMap<u32, Vec<u32>>,
550 inn: HashMap<u32, Vec<u32>>,
551 }
552 #[derive(Serialize, Deserialize, PartialEq, Debug)]
553 struct Wire {
554 by_type: HashMap<u32, WireAdj>,
555 edge_count: u64,
556 }
557
558 let mut by_type = HashMap::new();
559 by_type.insert(
560 0,
561 WireAdj {
562 out: HashMap::from([(5, vec![3, 9])]),
563 inn: HashMap::from([(3, vec![5]), (9, vec![5])]),
564 },
565 );
566 let wire = Wire {
567 by_type,
568 edge_count: 2,
569 };
570 let encoded = bincode::serialize(&wire).unwrap();
571 let t: Topology = bincode::deserialize(&encoded).unwrap();
572 assert_eq!(t.neighbors(0, Direction::Out, 5).as_ref(), &[3, 9]);
573 assert_eq!(t.neighbors(0, Direction::In, 3).as_ref(), &[5]);
574 assert_eq!(t.neighbors(0, Direction::In, 9).as_ref(), &[5]);
575 assert_eq!(t.edge_count(), 2);
576 assert!(matches!(
577 t.neighbors(0, Direction::Out, 5),
578 Cow::Borrowed(_)
579 ));
580
581 let roundtrip: Wire = bincode::deserialize(&bincode::serialize(&t).unwrap()).unwrap();
582 assert_eq!(roundtrip.edge_count, 2);
583 assert_eq!(roundtrip.by_type[&0].out[&5], vec![3, 9]);
584 assert_eq!(roundtrip.by_type[&0].inn[&3], vec![5]);
585 assert_eq!(roundtrip.by_type[&0].inn[&9], vec![5]);
586
587 let mut dirty = Topology::new();
589 for dst in (0u32..10).rev() {
590 dirty.add_edge(1, 0, dst);
591 }
592 assert!(matches!(
593 dirty.neighbors(1, Direction::Out, 0),
594 Cow::Owned(_)
595 ));
596 let dirty_wire: Wire = bincode::deserialize(&bincode::serialize(&dirty).unwrap()).unwrap();
597 assert_eq!(dirty_wire.edge_count, 10);
598 assert_eq!(
599 dirty_wire.by_type[&1].out[&0],
600 (0..10).collect::<Vec<u32>>()
601 );
602 for dst in 0..10 {
603 assert_eq!(dirty_wire.by_type[&1].inn[&dst], vec![0]);
604 }
605 }
606
607 #[test]
608 fn pack_roundtrip_merges_delta_and_restores_frozen() {
609 let mut t = Topology::new();
610 for dst in (0u32..10).rev() {
611 t.add_edge(2, 1, dst);
612 }
613 t.add_edge(0, 5, 9);
614 assert!(matches!(t.neighbors(2, Direction::Out, 1), Cow::Owned(_)));
615 let mut buf = Vec::new();
616 t.pack(&mut buf);
617 let (back, consumed) = Topology::unpack(&buf).unwrap();
618 assert_eq!(consumed, buf.len());
619 assert_eq!(back.edge_count(), 11);
620 let expected: Vec<u32> = (0..10).collect();
621 assert_eq!(
622 back.neighbors(2, Direction::Out, 1).as_ref(),
623 expected.as_slice()
624 );
625 assert!(matches!(
626 back.neighbors(2, Direction::Out, 1),
627 Cow::Borrowed(_)
628 ));
629 assert_eq!(back.neighbors(0, Direction::Out, 5).as_ref(), &[9]);
630 assert_eq!(back.neighbors(0, Direction::In, 9).as_ref(), &[5]);
631 assert_eq!(back.etypes().collect::<Vec<_>>(), vec![0, 2]);
632 }
633}