1use super::csr_edges::CsrEdges;
2use super::packed_coord::PackedCoord;
3use super::vertex::VertexId;
4use rustc_hash::{FxHashMap, FxHashSet};
5
6#[cfg(test)]
7mod tests {
8 use super::*;
9 use crate::engine::packed_coord::PackedCoord;
10
11 #[test]
12 fn test_delta_slab_add_edge() {
13 let csr = CsrEdges::from_adjacency(
14 vec![(0u32, vec![1u32])],
15 &[
16 PackedCoord::new(0, 0),
17 PackedCoord::new(0, 1),
18 PackedCoord::new(0, 2),
19 ],
20 );
21 let mut delta = DeltaEdgeSlab::new();
22
23 delta.add_edge(VertexId(0), VertexId(2));
24
25 let merged = delta.merged_view(&csr, VertexId(0));
26 assert_eq!(merged, vec![VertexId(1), VertexId(2)]);
27 }
28
29 #[test]
30 fn test_delta_slab_remove_edge() {
31 let csr = CsrEdges::from_adjacency(
32 vec![(0u32, vec![1u32, 2u32, 3u32])],
33 &[
34 PackedCoord::new(0, 0),
35 PackedCoord::new(0, 1),
36 PackedCoord::new(0, 2),
37 PackedCoord::new(0, 3),
38 ],
39 );
40 let mut delta = DeltaEdgeSlab::new();
41
42 delta.remove_edge(VertexId(0), VertexId(2));
43
44 let merged = delta.merged_view(&csr, VertexId(0));
45 assert_eq!(merged, vec![VertexId(1), VertexId(3)]);
46 }
47
48 #[test]
49 fn test_delta_slab_rebuild_threshold() {
50 let mut edges = CsrMutableEdges::new();
51
52 for i in 0..1000 {
54 edges.add_edge(VertexId(i), VertexId(i + 1));
55 }
56
57 assert!(edges.delta_size() < 100); }
60
61 #[test]
62 fn test_delta_slab_multiple_operations() {
63 let csr = CsrEdges::from_adjacency(
64 vec![(0u32, vec![1u32, 2u32]), (1u32, vec![3u32])],
65 &[
66 PackedCoord::new(0, 0),
67 PackedCoord::new(0, 1),
68 PackedCoord::new(0, 2),
69 PackedCoord::new(1, 0),
70 ],
71 );
72 let mut delta = DeltaEdgeSlab::new();
73
74 delta.add_edge(VertexId(0), VertexId(3));
76 delta.remove_edge(VertexId(0), VertexId(1));
77 delta.add_edge(VertexId(0), VertexId(4));
78
79 let merged = delta.merged_view(&csr, VertexId(0));
80 assert_eq!(merged, vec![VertexId(2), VertexId(3), VertexId(4)]);
81 }
82
83 #[test]
84 fn test_delta_slab_empty_base() {
85 let csr = CsrEdges::empty();
86 let mut delta = DeltaEdgeSlab::new();
87
88 delta.add_edge(VertexId(0), VertexId(1));
89 delta.add_edge(VertexId(0), VertexId(2));
90
91 let merged = delta.merged_view(&csr, VertexId(0));
92 assert_eq!(merged, vec![VertexId(1), VertexId(2)]);
93 }
94
95 #[test]
96 fn test_delta_slab_remove_nonexistent() {
97 let csr = CsrEdges::from_adjacency(
98 vec![(0u32, vec![1u32])],
99 &[PackedCoord::new(0, 0), PackedCoord::new(0, 1)],
100 );
101 let mut delta = DeltaEdgeSlab::new();
102
103 delta.remove_edge(VertexId(0), VertexId(2));
105
106 let merged = delta.merged_view(&csr, VertexId(0));
107 assert_eq!(merged, vec![VertexId(1)]); }
109
110 #[test]
111 fn test_delta_slab_apply_to_csr() {
112 let csr = CsrEdges::from_adjacency(
113 vec![(0u32, vec![1u32]), (1u32, vec![2u32]), (2u32, vec![])],
114 &[
115 PackedCoord::new(0, 0),
116 PackedCoord::new(0, 1),
117 PackedCoord::new(1, 0),
118 ],
119 );
120
121 let mut delta = DeltaEdgeSlab::new();
122 delta.add_edge(VertexId(0), VertexId(2));
123 delta.remove_edge(VertexId(1), VertexId(2));
124 delta.add_edge(VertexId(2), VertexId(0));
125
126 let coords = vec![
128 PackedCoord::new(0, 0),
129 PackedCoord::new(0, 1),
130 PackedCoord::new(1, 0),
131 ];
132 let vertex_ids = vec![0u32, 1u32, 2u32];
133 let new_csr = delta.apply_to_csr(&csr, &coords, &vertex_ids);
134
135 assert_eq!(new_csr.out_edges(VertexId(0)), &[VertexId(1), VertexId(2)]);
136 assert_eq!(new_csr.out_edges(VertexId(1)), &[]);
137 assert_eq!(new_csr.out_edges(VertexId(2)), &[VertexId(0)]);
138 }
139
140 #[test]
141 fn test_mutable_edges_auto_rebuild() {
142 let mut edges = CsrMutableEdges::with_coords(vec![
143 PackedCoord::new(0, 0),
144 PackedCoord::new(0, 1),
145 PackedCoord::new(1, 0),
146 ]);
147
148 edges.add_edge(VertexId(0), VertexId(1));
150 edges.add_edge(VertexId(1), VertexId(2));
151
152 for _ in 0..500 {
154 edges.add_edge(VertexId(2), VertexId(0));
155 edges.remove_edge(VertexId(2), VertexId(0));
156 }
157
158 assert!(edges.delta_size() < 50);
160
161 assert_eq!(edges.out_edges(VertexId(0)), vec![VertexId(1)]);
163 assert_eq!(edges.out_edges(VertexId(1)), vec![VertexId(2)]);
164 }
165
166 #[test]
167 fn test_mutable_edges_with_offset_vertex_ids() {
168 use crate::engine::vertex_store::FIRST_NORMAL_VERTEX;
169
170 let mut edges = CsrMutableEdges::new();
171
172 let base_id = FIRST_NORMAL_VERTEX;
174 edges.add_vertex(PackedCoord::new(0, 0), base_id);
175 edges.add_vertex(PackedCoord::new(0, 1), base_id + 1);
176 edges.add_vertex(PackedCoord::new(1, 0), base_id + 2);
177
178 edges.add_edge(VertexId(base_id), VertexId(base_id + 1));
180 edges.add_edge(VertexId(base_id + 1), VertexId(base_id + 2));
181 edges.add_edge(VertexId(base_id + 2), VertexId(base_id));
182
183 assert_eq!(
185 edges.out_edges(VertexId(base_id)),
186 vec![VertexId(base_id + 1)]
187 );
188 assert_eq!(
189 edges.out_edges(VertexId(base_id + 1)),
190 vec![VertexId(base_id + 2)]
191 );
192 assert_eq!(
193 edges.out_edges(VertexId(base_id + 2)),
194 vec![VertexId(base_id)]
195 );
196
197 edges.rebuild();
199 assert_eq!(
200 edges.out_edges(VertexId(base_id)),
201 vec![VertexId(base_id + 1)]
202 );
203 assert_eq!(
204 edges.out_edges(VertexId(base_id + 1)),
205 vec![VertexId(base_id + 2)]
206 );
207 assert_eq!(
208 edges.out_edges(VertexId(base_id + 2)),
209 vec![VertexId(base_id)]
210 );
211 }
212
213 #[test]
214 fn test_csr_coord_update() {
215 let mut edges = CsrMutableEdges::new();
216
217 edges.add_vertex(PackedCoord::new(1, 1), 1024);
218 edges.add_vertex(PackedCoord::new(2, 2), 1025);
219 edges.add_edge(VertexId(1024), VertexId(1025));
220
221 edges.update_coord(VertexId(1024), PackedCoord::new(5, 5));
223
224 edges.rebuild();
226 let out = edges.out_edges(VertexId(1024));
227 assert_eq!(out, vec![VertexId(1025)]);
228 }
229
230 #[test]
231 fn test_last_op_wins_add_then_remove() {
232 let csr = CsrEdges::from_adjacency(vec![(0u32, vec![])], &[PackedCoord::new(0, 0)]);
233 let mut delta = DeltaEdgeSlab::new();
234 delta.add_edge(VertexId(0), VertexId(1));
235 delta.remove_edge(VertexId(0), VertexId(1));
236 let merged = delta.merged_view(&csr, VertexId(0));
237 assert_eq!(merged, vec![]);
238 }
239
240 #[test]
241 fn test_last_op_wins_remove_then_add() {
242 let csr = CsrEdges::from_adjacency(vec![(0u32, vec![])], &[PackedCoord::new(0, 0)]);
243 let mut delta = DeltaEdgeSlab::new();
244 delta.remove_edge(VertexId(0), VertexId(1));
245 delta.add_edge(VertexId(0), VertexId(1));
246 let merged = delta.merged_view(&csr, VertexId(0));
247 assert_eq!(merged, vec![VertexId(1)]);
248 }
249
250 #[test]
251 fn test_dedup_additions_and_sorted() {
252 let csr = CsrEdges::from_adjacency(
253 vec![(0u32, vec![2u32])],
254 &[
255 PackedCoord::new(0, 0),
256 PackedCoord::new(0, 1),
257 PackedCoord::new(0, 2),
258 ],
259 );
260 let mut delta = DeltaEdgeSlab::new();
261 delta.add_edge(VertexId(0), VertexId(1));
263 delta.add_edge(VertexId(0), VertexId(3));
264 delta.add_edge(VertexId(0), VertexId(1)); let merged = delta.merged_view(&csr, VertexId(0));
266 assert_eq!(merged, vec![VertexId(1), VertexId(2), VertexId(3)]);
268 }
269
270 #[test]
271 fn test_end_batch_rebuilds_on_coord_change_only() {
272 let mut edges =
273 CsrMutableEdges::with_coords(vec![PackedCoord::new(0, 0), PackedCoord::new(0, 1)]);
274 edges.begin_batch();
275 edges.update_coord(VertexId(0), PackedCoord::new(0, 2));
276 edges.end_batch();
278 assert_eq!(edges.out_edges(VertexId(0)), Vec::<VertexId>::new());
280 }
281}
282
283#[derive(Debug)]
288pub struct DeltaEdgeSlab {
289 additions: FxHashMap<VertexId, FxHashSet<VertexId>>,
291
292 removals: FxHashMap<VertexId, FxHashSet<VertexId>>,
294
295 op_count: usize,
297
298 coord_changed: bool,
300}
301
302impl DeltaEdgeSlab {
303 pub fn new() -> Self {
305 Self {
306 additions: FxHashMap::default(),
307 removals: FxHashMap::default(),
308 op_count: 0,
309 coord_changed: false,
310 }
311 }
312
313 pub fn add_edge(&mut self, from: VertexId, to: VertexId) {
315 if let Some(rem) = self.removals.get_mut(&from) {
317 rem.remove(&to);
318 }
319 self.additions.entry(from).or_default().insert(to);
321 self.op_count += 1;
322 }
323
324 pub fn remove_edge(&mut self, from: VertexId, to: VertexId) {
326 if let Some(adds) = self.additions.get_mut(&from) {
328 adds.remove(&to);
329 }
330 self.removals.entry(from).or_default().insert(to);
332 self.op_count += 1;
333 }
334
335 pub fn merged_view(&self, csr: &CsrEdges, v: VertexId) -> Vec<VertexId> {
337 let mut result: Vec<_> = csr.out_edges(v).to_vec();
339 if let Some(removes) = self.removals.get(&v) {
341 result.retain(|e| !removes.contains(e));
342 }
343 if let Some(adds) = self.additions.get(&v) {
345 result.extend(adds.iter().copied());
346 }
347 let mut seen: FxHashSet<VertexId> = FxHashSet::default();
349 result.retain(|e| seen.insert(*e));
350 result.sort_by_key(|e| e.0);
351 result
352 }
353
354 pub fn needs_rebuild(&self) -> bool {
356 self.op_count >= 1000 || self.coord_changed
357 }
358
359 pub fn mark_dirty(&mut self) {
361 self.coord_changed = true;
362 }
363
364 pub fn op_count(&self) -> usize {
366 self.op_count
367 }
368
369 pub fn clear(&mut self) {
371 self.additions.clear();
372 self.removals.clear();
373 self.op_count = 0;
374 self.coord_changed = false;
375 }
376
377 pub fn apply_to_csr(
379 &self,
380 base: &CsrEdges,
381 coords: &[PackedCoord],
382 vertex_ids: &[u32],
383 ) -> CsrEdges {
384 let mut adjacency = Vec::with_capacity(vertex_ids.len());
385
386 for &vid in vertex_ids {
388 let v = VertexId(vid);
389 let merged = self.merged_view(base, v);
390
391 let targets: Vec<u32> = merged.into_iter().map(|id| id.0).collect();
393
394 adjacency.push((vid, targets));
395 }
396
397 CsrEdges::from_adjacency(adjacency, coords)
398 }
399}
400
401impl Default for DeltaEdgeSlab {
402 fn default() -> Self {
403 Self::new()
404 }
405}
406
407#[derive(Debug)]
412pub struct CsrMutableEdges {
413 base: CsrEdges,
415
416 delta: DeltaEdgeSlab,
418
419 coords: Vec<PackedCoord>,
421
422 vertex_ids: Vec<u32>,
424
425 batch_mode: bool,
427}
428
429impl CsrMutableEdges {
430 pub fn new() -> Self {
432 Self {
433 base: CsrEdges::empty(),
434 delta: DeltaEdgeSlab::new(),
435 coords: Vec::new(),
436 vertex_ids: Vec::new(),
437 batch_mode: false,
438 }
439 }
440
441 pub fn with_coords(coords: Vec<PackedCoord>) -> Self {
443 let num_vertices = coords.len();
444 let vertex_ids: Vec<u32> = (0..num_vertices as u32).collect();
445 let adjacency: Vec<_> = vertex_ids.iter().map(|&id| (id, Vec::new())).collect();
446
447 Self {
448 base: CsrEdges::from_adjacency(adjacency, &coords),
449 delta: DeltaEdgeSlab::new(),
450 coords,
451 vertex_ids,
452 batch_mode: false,
453 }
454 }
455
456 pub fn add_edge(&mut self, from: VertexId, to: VertexId) {
458 self.delta.add_edge(from, to);
459 self.maybe_rebuild();
460 }
461
462 pub fn remove_edge(&mut self, from: VertexId, to: VertexId) {
464 self.delta.remove_edge(from, to);
465 self.maybe_rebuild();
466 }
467
468 pub fn out_edges(&self, v: VertexId) -> Vec<VertexId> {
470 self.delta.merged_view(&self.base, v)
471 }
472
473 pub fn in_edges(&self, v: VertexId) -> &[VertexId] {
476 self.base.in_edges(v)
477 }
478
479 pub fn delta_size(&self) -> usize {
481 self.delta.op_count()
482 }
483
484 pub fn rebuild(&mut self) {
486 if self.delta.op_count() > 0 || self.delta.needs_rebuild() {
487 self.base = self
488 .delta
489 .apply_to_csr(&self.base, &self.coords, &self.vertex_ids);
490 self.delta.clear();
491 }
492 }
493
494 fn maybe_rebuild(&mut self) {
496 if !self.batch_mode && self.delta.needs_rebuild() {
497 self.rebuild();
498 }
499 }
500
501 pub fn begin_batch(&mut self) {
503 self.batch_mode = true;
504 }
505
506 pub fn end_batch(&mut self) {
508 self.batch_mode = false;
509 if self.delta.op_count() > 0 || self.delta.needs_rebuild() {
510 self.rebuild();
511 }
512 }
513
514 pub fn add_vertex(&mut self, coord: PackedCoord, vertex_id: u32) -> usize {
516 let idx = self.coords.len();
517 self.coords.push(coord);
518 self.vertex_ids.push(vertex_id);
519
520 self.rebuild();
523
524 idx
525 }
526
527 pub fn add_vertices_batch(&mut self, items: &[(PackedCoord, u32)]) {
529 if items.is_empty() {
530 return;
531 }
532 let start_len = self.coords.len();
533 self.coords.reserve(items.len());
534 self.vertex_ids.reserve(items.len());
535 for (coord, vid) in items {
536 self.coords.push(*coord);
537 self.vertex_ids.push(*vid);
538 }
539 self.rebuild();
541 debug_assert_eq!(self.coords.len(), start_len + items.len());
542 }
543
544 pub fn update_coord(&mut self, vertex_id: VertexId, new_coord: PackedCoord) {
547 if let Some(pos) = self.vertex_ids.iter().position(|&id| id == vertex_id.0) {
549 self.coords[pos] = new_coord;
550 self.delta.mark_dirty();
552 }
553 }
554
555 pub fn build_from_adjacency(
558 &mut self,
559 adjacency: Vec<(u32, Vec<u32>)>,
560 coords: Vec<PackedCoord>,
561 vertex_ids: Vec<u32>,
562 ) {
563 self.base = CsrEdges::from_adjacency(adjacency, &coords);
564 self.coords = coords;
565 self.vertex_ids = vertex_ids;
566 self.delta.clear();
567 }
568}
569
570impl Default for CsrMutableEdges {
571 fn default() -> Self {
572 Self::new()
573 }
574}