linkrs_storage/edge/mutable_csr/
overflow.rs1use super::super::{csr_shared::OverflowTable, ColdStamps, HotNbr, Nbr};
2
3#[derive(Debug, Clone, Default)]
9pub struct OverflowChunk {
10 hot: Vec<HotNbr>,
11 cold: Vec<ColdStamps>,
12}
13
14impl OverflowChunk {
15 pub fn with_capacity(cap: usize) -> Self {
16 Self {
17 hot: Vec::with_capacity(cap),
18 cold: Vec::with_capacity(cap),
19 }
20 }
21
22 #[inline]
23 pub fn len(&self) -> usize {
24 self.hot.len()
25 }
26
27 #[inline]
28 pub fn is_empty(&self) -> bool {
29 self.hot.is_empty()
30 }
31
32 #[inline]
34 pub fn capacity(&self) -> usize {
35 self.hot.capacity()
36 }
37
38 #[inline]
40 pub fn slot_at(&self, index: usize) -> Option<Nbr> {
41 Some(Nbr::from_parts(
42 *self.hot.get(index)?,
43 *self.cold.get(index)?,
44 ))
45 }
46
47 #[inline]
49 pub fn hot_at(&self, index: usize) -> Option<HotNbr> {
50 self.hot.get(index).copied()
51 }
52
53 #[inline]
55 pub fn cold_at(&self, index: usize) -> Option<ColdStamps> {
56 self.cold.get(index).copied()
57 }
58
59 #[inline]
61 pub fn hot_slice(&self) -> &[HotNbr] {
62 &self.hot
63 }
64
65 #[inline]
67 pub fn cold_slice(&self) -> &[ColdStamps] {
68 &self.cold
69 }
70
71 #[inline]
73 pub fn cold_at_mut(&mut self, index: usize) -> Option<&mut ColdStamps> {
74 self.cold.get_mut(index)
75 }
76
77 #[inline]
79 pub fn push(&mut self, nbr: Nbr) {
80 self.hot.push(nbr.hot());
81 self.cold.push(nbr.cold());
82 debug_assert_eq!(self.hot.len(), self.cold.len());
83 }
84
85 #[inline]
90 pub fn remove(&mut self, index: usize) -> Nbr {
91 let hot = self.hot.swap_remove(index);
92 let cold = self.cold.swap_remove(index);
93 Nbr::from_parts(hot, cold)
94 }
95
96 pub fn extend_from_slice(&mut self, slots: &[Nbr]) {
98 self.hot.reserve(slots.len());
99 self.cold.reserve(slots.len());
100 for nbr in slots {
101 self.hot.push(nbr.hot());
102 self.cold.push(nbr.cold());
103 }
104 }
105
106 pub fn consolidated(slots: &[Nbr]) -> Self {
113 let mut chunk = OverflowChunk::default();
114 chunk.extend_from_slice(slots);
115 chunk
116 }
117
118 pub fn consolidated_with_reserve(slots: &[Nbr], spare: usize) -> Self {
126 let mut chunk = OverflowChunk::default();
127 chunk.hot.reserve(slots.len().saturating_add(spare));
128 chunk.cold.reserve(slots.len().saturating_add(spare));
129 chunk.extend_from_slice(slots);
130 chunk
131 }
132}
133
134pub(crate) const OVERFLOW_REPACK_CHUNKS_PER_VERTEX: usize = 8;
147
148impl super::super::csr_shared::OverflowChunkSpec for OverflowChunk {
149 type Slot = Nbr;
150
151 fn with_capacity(cap: usize) -> Self {
152 OverflowChunk::with_capacity(cap)
153 }
154
155 #[inline]
156 fn len(&self) -> usize {
157 self.len()
158 }
159
160 #[inline]
161 fn capacity(&self) -> usize {
162 self.capacity()
163 }
164
165 #[inline]
166 fn push_slot(&mut self, slot: Nbr) {
167 self.push(slot);
168 }
169}
170
171pub type OverflowStorage = OverflowTable<OverflowChunk>;
174
175#[cfg(test)]
176mod tests {
177 use super::super::super::csr_shared::SEGMENT_SIZE;
178 use super::*;
179 use linkrs_core::types::EdgeId;
180
181 fn chunks_with_first_endpoint(endpoint: u32) -> Vec<OverflowChunk> {
182 let mut chunk = OverflowChunk::default();
183 chunk.push(Nbr::new(endpoint, 0, EdgeId::new(1)));
184 vec![chunk]
185 }
186
187 #[test]
188 fn empty_vertices_cost_only_pointers() {
189 let mut storage = OverflowStorage::new();
190 storage.ensure_capacity(1_000_000);
191 assert!(storage.is_empty());
192 assert!(storage.index_bytes() < 1_000_000);
195 assert!(storage.get(999_999).is_none());
196 }
197
198 #[test]
199 fn routes_across_segment_boundary() {
200 let mut storage = OverflowStorage::new();
201 let lo = SEGMENT_SIZE as u32 - 1;
202 let hi = SEGMENT_SIZE as u32;
203 let far = 2 * SEGMENT_SIZE as u32 + 5;
204 storage.insert(lo, chunks_with_first_endpoint(10));
205 storage.insert(hi, chunks_with_first_endpoint(20));
206 storage.insert(far, chunks_with_first_endpoint(30));
207 assert_eq!(storage.len(), 3);
208 for (vid, endpoint) in [(lo, 10), (hi, 20), (far, 30)] {
209 let chunks = storage.get(vid).expect("row present");
210 assert_eq!(chunks[0].hot_at(0).expect("slot").endpoint, endpoint);
211 }
212 let taken = storage.remove(hi).expect("row removed");
213 assert_eq!(taken[0].hot_at(0).expect("slot").endpoint, 20);
214 assert_eq!(storage.len(), 2);
215 assert!(storage.get(hi).is_none());
216 }
217
218 #[test]
219 fn iter_walks_rows_in_vertex_order() {
220 let mut storage = OverflowStorage::new();
221 let far = 3 * SEGMENT_SIZE as u32 + 7;
222 let near = 5u32;
223 storage.insert(far, chunks_with_first_endpoint(30));
224 storage.insert(near, chunks_with_first_endpoint(10));
225 storage.insert(SEGMENT_SIZE as u32, chunks_with_first_endpoint(20));
226 let order: Vec<u32> = storage.iter().map(|(vid, _)| vid).collect();
227 assert_eq!(order, vec![near, SEGMENT_SIZE as u32, far]);
228 }
229
230 #[test]
231 fn clear_empties_allocated_segments() {
232 let mut storage = OverflowStorage::new();
233 storage.insert(SEGMENT_SIZE as u32 + 1, chunks_with_first_endpoint(1));
234 storage.clear();
235 assert!(storage.is_empty());
236 assert_eq!(storage.len(), 0);
237 assert!(storage.get(SEGMENT_SIZE as u32 + 1).is_none());
238 storage.insert(3, chunks_with_first_endpoint(2));
239 assert_eq!(storage.len(), 1);
240 }
241}