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 etypes(&self) -> impl Iterator<Item = u32> + '_ {
233 let mut ids: Vec<u32> = self.by_type.keys().copied().collect();
234 ids.sort_unstable();
235 ids.into_iter()
236 }
237
238 pub fn all_edges(&self) -> impl Iterator<Item = (u32, u32, u32)> + '_ {
244 self.by_type.iter().flat_map(|(&etype, adj)| {
245 adj.out.iter().flat_map(move |(&src, al)| {
246 al.merged()
247 .into_owned()
248 .into_iter()
249 .map(move |dst| (etype, src, dst))
250 })
251 })
252 }
253
254 pub fn remove_edge(&mut self, etype: u32, src: u32, dst: u32) -> RemoveEdgeOutcome {
263 let found_in_overlay = (|| {
264 let adj = self.by_type.get_mut(&etype)?;
265 let dsts = adj.out.get_mut(&src)?;
266 if !dsts.remove(dst) {
267 return None;
268 }
269 let srcs = adj
270 .inn
271 .get_mut(&dst)
272 .expect("invariant: inn bucket must exist when out contains dst");
273 assert!(
274 srcs.remove(src),
275 "invariant: inn must contain src when out contained dst"
276 );
277 self.edge_count -= 1;
278 Some(())
279 })()
280 .is_some();
281
282 if !found_in_overlay {
283 self.out_tombstones
287 .entry(etype)
288 .or_default()
289 .entry(src)
290 .or_default()
291 .insert(dst);
292 self.in_tombstones
293 .entry(etype)
294 .or_default()
295 .entry(dst)
296 .or_default()
297 .insert(src);
298 RemoveEdgeOutcome::TombstonedBase
299 } else {
300 RemoveEdgeOutcome::RemovedFromOverlay
301 }
302 }
303
304 pub fn out_tombstones_for(&self, etype: u32, src: u32) -> Option<&BTreeSet<u32>> {
308 self.out_tombstones.get(&etype)?.get(&src)
309 }
310
311 pub fn in_tombstones_for(&self, etype: u32, dst: u32) -> Option<&BTreeSet<u32>> {
313 self.in_tombstones.get(&etype)?.get(&dst)
314 }
315
316 fn adj_list(&self, etype: u32, dir: Direction, v: u32) -> Option<&AdjList> {
317 self.by_type.get(&etype).and_then(|adj| match dir {
318 Direction::Out => adj.out.get(&v),
319 Direction::In => adj.inn.get(&v),
320 })
321 }
322
323 pub(crate) fn pack(&self, out: &mut Vec<u8>) {
327 let mut etypes: Vec<u32> = self.by_type.keys().copied().collect();
328 etypes.sort_unstable();
329 push_u32(out, etypes.len() as u32);
330 for et in etypes {
331 push_u32(out, et);
332 let adj = &self.by_type[&et];
333 pack_adj_map(out, &adj.out);
334 pack_adj_map(out, &adj.inn);
335 }
336 push_u64(out, self.edge_count);
337 }
338
339 pub(crate) fn unpack(src: &[u8]) -> StoreResult<(Self, usize)> {
340 let mut pos = 0usize;
341 let n_etypes = read_u32(src, &mut pos)? as usize;
342 let mut by_type = HashMap::with_capacity(n_etypes);
343 for _ in 0..n_etypes {
344 let et = read_u32(src, &mut pos)?;
345 let out = unpack_adj_map(src, &mut pos)?;
346 let inn = unpack_adj_map(src, &mut pos)?;
347 by_type.insert(et, TypedAdjacency { out, inn });
348 }
349 let edge_count = read_u64(src, &mut pos)?;
350 Ok((
351 Self {
352 by_type,
353 edge_count,
354 out_tombstones: HashMap::new(),
355 in_tombstones: HashMap::new(),
356 },
357 pos,
358 ))
359 }
360}
361
362fn pack_adj_map(out: &mut Vec<u8>, map: &HashMap<u32, AdjList>) {
363 let mut verts: Vec<u32> = map.keys().copied().collect();
364 verts.sort_unstable();
365 push_u32(out, verts.len() as u32);
366 for v in verts {
367 push_u32(out, v);
368 let list = &map[&v];
369 push_u32s(out, list.merged().as_ref());
370 }
371}
372
373fn unpack_adj_map(src: &[u8], pos: &mut usize) -> StoreResult<HashMap<u32, AdjList>> {
374 let n = read_u32(src, pos)? as usize;
375 let mut map = HashMap::with_capacity(n);
376 for _ in 0..n {
377 let v = read_u32(src, pos)?;
378 let frozen = read_u32s(src, pos)?;
379 map.insert(
380 v,
381 AdjList {
382 frozen,
383 delta: Vec::new(),
384 },
385 );
386 }
387 Ok(map)
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 #[test]
395 fn edges_are_typed_directed_sorted_deduped() {
396 let mut t = Topology::new();
397 assert!(t.add_edge(0, 5, 9));
398 assert!(t.add_edge(0, 5, 3));
399 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]);
403 assert_eq!(t.neighbors(0, Direction::Out, 999).as_ref(), &[] as &[u32]);
404 assert_eq!(t.degree(0, Direction::Out, 5), 2);
405 assert_eq!(t.edge_count(), 3);
406 }
407
408 #[test]
409 fn remove_edge_updates_both_sides_and_count() {
410 let mut t = Topology::new();
411 t.add_edge(0, 1, 2);
412 t.add_edge(0, 1, 3);
413 assert_eq!(
414 t.remove_edge(0, 1, 2),
415 RemoveEdgeOutcome::RemovedFromOverlay
416 );
417 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]);
420 assert_eq!(t.neighbors(0, Direction::In, 2).as_ref(), &[] as &[u32]);
421 assert_eq!(t.edge_count(), 1);
422 assert!(t.add_edge(0, 1, 2));
424 assert_eq!(t.edge_count(), 2);
425 }
426
427 #[test]
428 fn etypes_empty_multiple_and_sorted() {
429 let empty = Topology::new();
430 assert_eq!(empty.etypes().collect::<Vec<_>>(), Vec::<u32>::new());
431
432 let mut t = Topology::new();
433 t.add_edge(3, 0, 1);
434 t.add_edge(1, 0, 1);
435 t.add_edge(3, 1, 2); t.add_edge(2, 0, 2);
437 assert_eq!(t.etypes().collect::<Vec<_>>(), vec![1, 2, 3]);
438 }
439
440 #[test]
441 fn insert_buffer_defers_sort_until_threshold_then_neighbors_sorted_unique() {
442 let mut t = Topology::new();
443 for dst in (0u32..32).rev() {
445 assert!(t.add_edge(0, 0, dst));
446 let nbrs = t.neighbors(0, Direction::Out, 0);
447 assert!(
448 matches!(nbrs, Cow::Owned(_)),
449 "delta still dirty at {} edges; neighbors must take the owned merge path",
450 32 - dst
451 );
452 assert!(
453 nbrs.windows(2).all(|w| w[0] < w[1]),
454 "dirty merge must be sorted unique, got {nbrs:?}"
455 );
456 assert_eq!(nbrs.len(), (32 - dst) as usize);
457 let n = t.adj_list(0, Direction::Out, 0).unwrap();
458 assert!(
459 n.frozen.is_empty(),
460 "must not flush frozen before threshold"
461 );
462 assert_eq!(n.delta.len(), (32 - dst) as usize);
463 }
464 assert_eq!(t.degree(0, Direction::Out, 0), 32);
465 assert_eq!(t.neighbors(0, Direction::In, 31).as_ref(), &[0]);
466
467 assert!(t.add_edge(0, 0, 32)); let nbrs = t.neighbors(0, Direction::Out, 0);
469 assert!(
470 matches!(nbrs, Cow::Borrowed(_)),
471 "after threshold flush, scan path must borrow the frozen block"
472 );
473 let expected: Vec<u32> = (0..33).collect();
474 assert_eq!(nbrs.as_ref(), expected.as_slice());
475 let n = t.adj_list(0, Direction::Out, 0).unwrap();
476 assert!(n.delta.is_empty());
477 assert_eq!(n.frozen, expected);
478 assert_eq!(t.edge_count(), 33);
479 assert_eq!(t.neighbors(0, Direction::In, 32).as_ref(), &[0]);
480 assert!(!t.add_edge(0, 0, 7));
481 }
482
483 #[test]
484 fn remove_edge_from_delta_and_from_frozen() {
485 let mut t = Topology::new();
486 for dst in 0u32..10 {
487 assert!(t.add_edge(0, 1, dst));
488 }
489 assert!(matches!(t.neighbors(0, Direction::Out, 1), Cow::Owned(_)));
490 assert_eq!(
491 t.remove_edge(0, 1, 7),
492 RemoveEdgeOutcome::RemovedFromOverlay
493 );
494 assert_eq!(t.remove_edge(0, 1, 7), RemoveEdgeOutcome::TombstonedBase);
495 assert_eq!(t.neighbors(0, Direction::In, 7).as_ref(), &[] as &[u32]);
496 assert_eq!(
497 t.neighbors(0, Direction::Out, 1).as_ref(),
498 &[0, 1, 2, 3, 4, 5, 6, 8, 9]
499 );
500 assert_eq!(t.edge_count(), 9);
501 assert!(t.add_edge(0, 1, 7));
502 assert_eq!(t.edge_count(), 10);
503
504 let mut t = Topology::new();
505 for dst in 0u32..33 {
506 assert!(t.add_edge(0, 1, dst));
507 }
508 assert!(matches!(
509 t.neighbors(0, Direction::Out, 1),
510 Cow::Borrowed(_)
511 ));
512 assert_eq!(
513 t.remove_edge(0, 1, 0),
514 RemoveEdgeOutcome::RemovedFromOverlay
515 );
516 assert_eq!(
517 t.remove_edge(0, 1, 32),
518 RemoveEdgeOutcome::RemovedFromOverlay
519 );
520 assert_eq!(t.edge_count(), 31);
521 assert_eq!(t.neighbors(0, Direction::In, 0).as_ref(), &[] as &[u32]);
522 assert_eq!(t.neighbors(0, Direction::In, 16).as_ref(), &[1]);
523 let expected: Vec<u32> = (1..32).collect();
524 assert_eq!(
525 t.neighbors(0, Direction::Out, 1).as_ref(),
526 expected.as_slice()
527 );
528 assert!(t.add_edge(0, 1, 0));
529 assert_eq!(t.edge_count(), 32);
530 }
531
532 #[test]
533 fn serde_wire_is_hashmap_of_hashmap_of_vec() {
534 #[derive(Serialize, Deserialize, PartialEq, Debug)]
535 struct WireAdj {
536 out: HashMap<u32, Vec<u32>>,
537 inn: HashMap<u32, Vec<u32>>,
538 }
539 #[derive(Serialize, Deserialize, PartialEq, Debug)]
540 struct Wire {
541 by_type: HashMap<u32, WireAdj>,
542 edge_count: u64,
543 }
544
545 let mut by_type = HashMap::new();
546 by_type.insert(
547 0,
548 WireAdj {
549 out: HashMap::from([(5, vec![3, 9])]),
550 inn: HashMap::from([(3, vec![5]), (9, vec![5])]),
551 },
552 );
553 let wire = Wire {
554 by_type,
555 edge_count: 2,
556 };
557 let encoded = bincode::serialize(&wire).unwrap();
558 let t: Topology = bincode::deserialize(&encoded).unwrap();
559 assert_eq!(t.neighbors(0, Direction::Out, 5).as_ref(), &[3, 9]);
560 assert_eq!(t.neighbors(0, Direction::In, 3).as_ref(), &[5]);
561 assert_eq!(t.neighbors(0, Direction::In, 9).as_ref(), &[5]);
562 assert_eq!(t.edge_count(), 2);
563 assert!(matches!(
564 t.neighbors(0, Direction::Out, 5),
565 Cow::Borrowed(_)
566 ));
567
568 let roundtrip: Wire = bincode::deserialize(&bincode::serialize(&t).unwrap()).unwrap();
569 assert_eq!(roundtrip.edge_count, 2);
570 assert_eq!(roundtrip.by_type[&0].out[&5], vec![3, 9]);
571 assert_eq!(roundtrip.by_type[&0].inn[&3], vec![5]);
572 assert_eq!(roundtrip.by_type[&0].inn[&9], vec![5]);
573
574 let mut dirty = Topology::new();
576 for dst in (0u32..10).rev() {
577 dirty.add_edge(1, 0, dst);
578 }
579 assert!(matches!(
580 dirty.neighbors(1, Direction::Out, 0),
581 Cow::Owned(_)
582 ));
583 let dirty_wire: Wire = bincode::deserialize(&bincode::serialize(&dirty).unwrap()).unwrap();
584 assert_eq!(dirty_wire.edge_count, 10);
585 assert_eq!(
586 dirty_wire.by_type[&1].out[&0],
587 (0..10).collect::<Vec<u32>>()
588 );
589 for dst in 0..10 {
590 assert_eq!(dirty_wire.by_type[&1].inn[&dst], vec![0]);
591 }
592 }
593
594 #[test]
595 fn pack_roundtrip_merges_delta_and_restores_frozen() {
596 let mut t = Topology::new();
597 for dst in (0u32..10).rev() {
598 t.add_edge(2, 1, dst);
599 }
600 t.add_edge(0, 5, 9);
601 assert!(matches!(t.neighbors(2, Direction::Out, 1), Cow::Owned(_)));
602 let mut buf = Vec::new();
603 t.pack(&mut buf);
604 let (back, consumed) = Topology::unpack(&buf).unwrap();
605 assert_eq!(consumed, buf.len());
606 assert_eq!(back.edge_count(), 11);
607 let expected: Vec<u32> = (0..10).collect();
608 assert_eq!(
609 back.neighbors(2, Direction::Out, 1).as_ref(),
610 expected.as_slice()
611 );
612 assert!(matches!(
613 back.neighbors(2, Direction::Out, 1),
614 Cow::Borrowed(_)
615 ));
616 assert_eq!(back.neighbors(0, Direction::Out, 5).as_ref(), &[9]);
617 assert_eq!(back.neighbors(0, Direction::In, 9).as_ref(), &[5]);
618 assert_eq!(back.etypes().collect::<Vec<_>>(), vec![0, 2]);
619 }
620}