1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! `EdgeStore` persistence (serialization/deserialization) methods.
//!
//! Extracted from `edge.rs` to reduce NLOC below the 500 threshold.
use super::csr_snapshot::{CsrSnapshot, SnapshotBuilder};
use super::edge::EdgeStore;
use super::helpers::PostcardPersistence;
use super::label_table::LabelTable;
// ---------------------------------------------------------------------------
// CSR snapshot methods (G1: zero-copy BFS)
// ---------------------------------------------------------------------------
impl EdgeStore {
/// Builds a CSR snapshot from the current outgoing index.
///
/// This pre-computes contiguous arrays of target IDs, edge IDs, and
/// interned labels for all source nodes. After calling this, BFS
/// traversal uses `with_neighbors()` for zero-copy `&[u64]` access
/// instead of cloning full `GraphEdge` objects.
///
/// # When to call
///
/// - After loading from disk (graph is ready for reads)
/// - After a batch of mutations, before a read-heavy phase
///
/// The snapshot is automatically invalidated by any write operation.
pub fn build_read_snapshot(&mut self) {
let label_table = LabelTable::new();
self.csr_snapshot = Some(SnapshotBuilder::build(self, &label_table));
}
/// Returns a reference to the CSR snapshot, if built.
#[must_use]
#[inline]
pub fn csr_snapshot(&self) -> Option<&CsrSnapshot> {
self.csr_snapshot.as_ref()
}
/// Returns `true` if a CSR snapshot is available for zero-copy reads.
#[must_use]
#[inline]
pub fn has_csr_snapshot(&self) -> bool {
self.csr_snapshot.is_some()
}
/// Provides zero-copy access to neighbor target IDs via a callback.
#[inline]
pub fn with_neighbors<F, R>(&self, source_id: u64, f: F) -> R
where
F: FnOnce(&[u64]) -> R,
{
if let Some(snapshot) = &self.csr_snapshot {
f(snapshot.neighbors(source_id))
} else {
let ids: Vec<u64> = self
.get_outgoing(source_id)
.iter()
.map(|e| e.target())
.collect();
f(&ids)
}
}
/// Provides zero-copy access to `(target_id, edge_id)` pairs via callback.
#[inline]
pub fn with_neighbor_edges<F, R>(&self, source_id: u64, f: F) -> R
where
F: FnOnce(&[u64], &[u64]) -> R,
{
if let Some(snapshot) = &self.csr_snapshot {
f(snapshot.neighbors(source_id), snapshot.edge_ids(source_id))
} else {
let edges = self.get_outgoing(source_id);
let targets: Vec<u64> = edges.iter().map(|e| e.target()).collect();
let eids: Vec<u64> = edges.iter().map(|e| e.id()).collect();
f(&targets, &eids)
}
}
}
impl PostcardPersistence for EdgeStore {}
// Inherent persistence methods that delegate to `PostcardPersistence`.
impl EdgeStore {
/// Serializes the edge store to bytes using `postcard`.
///
/// # Errors
/// Returns an error if serialization fails.
pub fn to_bytes(&self) -> std::result::Result<Vec<u8>, postcard::Error> {
<Self as PostcardPersistence>::to_bytes(self)
}
/// Deserializes an edge store from bytes.
///
/// # Errors
/// Returns an error if deserialization fails (e.g., corrupted data).
pub fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, postcard::Error> {
<Self as PostcardPersistence>::from_bytes(bytes)
}
/// Saves the edge store to a file.
///
/// # Errors
/// Returns an error if serialization or file I/O fails.
pub fn save_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
<Self as PostcardPersistence>::save_to_file(self, path)
}
/// Loads an edge store from a file.
///
/// Automatically builds a CSR snapshot after loading for zero-copy
/// BFS traversal (G1).
///
/// # Errors
/// Returns an error if file I/O or deserialization fails.
pub fn load_from_file(path: &std::path::Path) -> std::io::Result<Self> {
let mut store = <Self as PostcardPersistence>::load_from_file(path)?;
store.build_read_snapshot();
Ok(store)
}
}