use super::csr_edges::CsrEdges;
use super::vertex::VertexId;
use formualizer_common::Coord as AbsCoord;
use rustc_hash::{FxHashMap, FxHashSet};
#[cfg(test)]
mod tests {
use super::*;
use formualizer_common::Coord as AbsCoord;
#[test]
fn test_delta_slab_add_edge() {
let csr = CsrEdges::from_adjacency(
vec![(0u32, vec![1u32])],
&[
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(0, 2),
],
);
let mut delta = DeltaEdgeSlab::new();
delta.add_edge(VertexId(0), VertexId(2));
let merged = delta.merged_view(&csr, VertexId(0));
assert_eq!(merged, vec![VertexId(1), VertexId(2)]);
}
#[test]
fn test_delta_slab_remove_edge() {
let csr = CsrEdges::from_adjacency(
vec![(0u32, vec![1u32, 2u32, 3u32])],
&[
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(0, 2),
AbsCoord::new(0, 3),
],
);
let mut delta = DeltaEdgeSlab::new();
delta.remove_edge(VertexId(0), VertexId(2));
let merged = delta.merged_view(&csr, VertexId(0));
assert_eq!(merged, vec![VertexId(1), VertexId(3)]);
}
#[test]
fn test_delta_slab_rebuild_threshold() {
let mut edges = CsrMutableEdges::new();
for i in 0..1000 {
edges.add_edge(VertexId(i), VertexId(i + 1));
}
assert!(edges.delta_size() < 100); }
#[test]
fn test_delta_slab_multiple_operations() {
let csr = CsrEdges::from_adjacency(
vec![(0u32, vec![1u32, 2u32]), (1u32, vec![3u32])],
&[
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(0, 2),
AbsCoord::new(1, 0),
],
);
let mut delta = DeltaEdgeSlab::new();
delta.add_edge(VertexId(0), VertexId(3));
delta.remove_edge(VertexId(0), VertexId(1));
delta.add_edge(VertexId(0), VertexId(4));
let merged = delta.merged_view(&csr, VertexId(0));
assert_eq!(merged, vec![VertexId(2), VertexId(3), VertexId(4)]);
}
#[test]
fn test_delta_slab_empty_base() {
let csr = CsrEdges::empty();
let mut delta = DeltaEdgeSlab::new();
delta.add_edge(VertexId(0), VertexId(1));
delta.add_edge(VertexId(0), VertexId(2));
let merged = delta.merged_view(&csr, VertexId(0));
assert_eq!(merged, vec![VertexId(1), VertexId(2)]);
}
#[test]
fn test_delta_slab_remove_nonexistent() {
let csr = CsrEdges::from_adjacency(
vec![(0u32, vec![1u32])],
&[AbsCoord::new(0, 0), AbsCoord::new(0, 1)],
);
let mut delta = DeltaEdgeSlab::new();
delta.remove_edge(VertexId(0), VertexId(2));
let merged = delta.merged_view(&csr, VertexId(0));
assert_eq!(merged, vec![VertexId(1)]); }
#[test]
fn test_delta_slab_apply_to_csr() {
let csr = CsrEdges::from_adjacency(
vec![(0u32, vec![1u32]), (1u32, vec![2u32]), (2u32, vec![])],
&[
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(1, 0),
],
);
let mut delta = DeltaEdgeSlab::new();
delta.add_edge(VertexId(0), VertexId(2));
delta.remove_edge(VertexId(1), VertexId(2));
delta.add_edge(VertexId(2), VertexId(0));
let coords = vec![
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(1, 0),
];
let vertex_ids = vec![0u32, 1u32, 2u32];
let new_csr = delta.apply_to_csr(&csr, &coords, &vertex_ids);
assert_eq!(new_csr.out_edges(VertexId(0)), &[VertexId(1), VertexId(2)]);
assert_eq!(new_csr.out_edges(VertexId(1)), &[]);
assert_eq!(new_csr.out_edges(VertexId(2)), &[VertexId(0)]);
}
#[test]
fn test_mutable_edges_auto_rebuild() {
let mut edges = CsrMutableEdges::with_coords(vec![
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(1, 0),
]);
edges.add_edge(VertexId(0), VertexId(1));
edges.add_edge(VertexId(1), VertexId(2));
for _ in 0..500 {
edges.add_edge(VertexId(2), VertexId(0));
edges.remove_edge(VertexId(2), VertexId(0));
}
assert!(edges.delta_size() < 50);
assert_eq!(edges.out_edges(VertexId(0)), vec![VertexId(1)]);
assert_eq!(edges.out_edges(VertexId(1)), vec![VertexId(2)]);
}
#[test]
fn test_mutable_edges_with_offset_vertex_ids() {
use crate::engine::vertex_store::FIRST_NORMAL_VERTEX;
let mut edges = CsrMutableEdges::new();
let base_id = FIRST_NORMAL_VERTEX;
edges.add_vertex(AbsCoord::new(0, 0), base_id);
edges.add_vertex(AbsCoord::new(0, 1), base_id + 1);
edges.add_vertex(AbsCoord::new(1, 0), base_id + 2);
edges.add_edge(VertexId(base_id), VertexId(base_id + 1));
edges.add_edge(VertexId(base_id + 1), VertexId(base_id + 2));
edges.add_edge(VertexId(base_id + 2), VertexId(base_id));
assert_eq!(
edges.out_edges(VertexId(base_id)),
vec![VertexId(base_id + 1)]
);
assert_eq!(
edges.out_edges(VertexId(base_id + 1)),
vec![VertexId(base_id + 2)]
);
assert_eq!(
edges.out_edges(VertexId(base_id + 2)),
vec![VertexId(base_id)]
);
edges.rebuild();
assert_eq!(
edges.out_edges(VertexId(base_id)),
vec![VertexId(base_id + 1)]
);
assert_eq!(
edges.out_edges(VertexId(base_id + 1)),
vec![VertexId(base_id + 2)]
);
assert_eq!(
edges.out_edges(VertexId(base_id + 2)),
vec![VertexId(base_id)]
);
}
#[test]
fn test_csr_coord_update() {
let mut edges = CsrMutableEdges::new();
edges.add_vertex(AbsCoord::new(1, 1), 1024);
edges.add_vertex(AbsCoord::new(2, 2), 1025);
edges.add_edge(VertexId(1024), VertexId(1025));
edges.update_coord(VertexId(1024), AbsCoord::new(5, 5));
edges.rebuild();
let out = edges.out_edges(VertexId(1024));
assert_eq!(out, vec![VertexId(1025)]);
}
#[test]
fn test_last_op_wins_add_then_remove() {
let csr = CsrEdges::from_adjacency(vec![(0u32, vec![])], &[AbsCoord::new(0, 0)]);
let mut delta = DeltaEdgeSlab::new();
delta.add_edge(VertexId(0), VertexId(1));
delta.remove_edge(VertexId(0), VertexId(1));
let merged = delta.merged_view(&csr, VertexId(0));
assert_eq!(merged, vec![]);
}
#[test]
fn test_last_op_wins_remove_then_add() {
let csr = CsrEdges::from_adjacency(vec![(0u32, vec![])], &[AbsCoord::new(0, 0)]);
let mut delta = DeltaEdgeSlab::new();
delta.remove_edge(VertexId(0), VertexId(1));
delta.add_edge(VertexId(0), VertexId(1));
let merged = delta.merged_view(&csr, VertexId(0));
assert_eq!(merged, vec![VertexId(1)]);
}
#[test]
fn test_dedup_additions_and_sorted() {
let csr = CsrEdges::from_adjacency(
vec![(0u32, vec![2u32])],
&[
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(0, 2),
],
);
let mut delta = DeltaEdgeSlab::new();
delta.add_edge(VertexId(0), VertexId(1));
delta.add_edge(VertexId(0), VertexId(3));
delta.add_edge(VertexId(0), VertexId(1)); let merged = delta.merged_view(&csr, VertexId(0));
assert_eq!(merged, vec![VertexId(1), VertexId(2), VertexId(3)]);
}
#[test]
fn test_end_batch_rebuilds_on_coord_change_only() {
let mut edges =
CsrMutableEdges::with_coords(vec![AbsCoord::new(0, 0), AbsCoord::new(0, 1)]);
edges.begin_batch();
edges.update_coord(VertexId(0), AbsCoord::new(0, 2));
edges.end_batch();
assert_eq!(edges.out_edges(VertexId(0)), Vec::<VertexId>::new());
}
}
#[derive(Debug)]
pub struct DeltaEdgeSlab {
additions: FxHashMap<VertexId, FxHashSet<VertexId>>,
removals: FxHashMap<VertexId, FxHashSet<VertexId>>,
op_count: usize,
coord_changed: bool,
}
impl DeltaEdgeSlab {
pub fn new() -> Self {
Self {
additions: FxHashMap::default(),
removals: FxHashMap::default(),
op_count: 0,
coord_changed: false,
}
}
pub fn add_edge(&mut self, from: VertexId, to: VertexId) {
if let Some(rem) = self.removals.get_mut(&from) {
rem.remove(&to);
}
self.additions.entry(from).or_default().insert(to);
self.op_count += 1;
}
pub fn remove_edge(&mut self, from: VertexId, to: VertexId) {
if let Some(adds) = self.additions.get_mut(&from) {
adds.remove(&to);
}
self.removals.entry(from).or_default().insert(to);
self.op_count += 1;
}
pub fn merged_view(&self, csr: &CsrEdges, v: VertexId) -> Vec<VertexId> {
let mut result: Vec<_> = csr.out_edges(v).to_vec();
if let Some(removes) = self.removals.get(&v) {
result.retain(|e| !removes.contains(e));
}
if let Some(adds) = self.additions.get(&v) {
result.extend(adds.iter().copied());
}
let mut seen: FxHashSet<VertexId> = FxHashSet::default();
result.retain(|e| seen.insert(*e));
result.sort_by_key(|e| e.0);
result
}
pub fn needs_rebuild(&self) -> bool {
self.op_count >= 1000 || self.coord_changed
}
pub fn mark_dirty(&mut self) {
self.coord_changed = true;
}
pub fn op_count(&self) -> usize {
self.op_count
}
pub fn clear(&mut self) {
self.additions.clear();
self.removals.clear();
self.op_count = 0;
self.coord_changed = false;
}
pub fn apply_to_csr(
&self,
base: &CsrEdges,
coords: &[AbsCoord],
vertex_ids: &[u32],
) -> CsrEdges {
let mut adjacency = Vec::with_capacity(vertex_ids.len());
for &vid in vertex_ids {
let v = VertexId(vid);
let merged = self.merged_view(base, v);
let targets: Vec<u32> = merged.into_iter().map(|id| id.0).collect();
adjacency.push((vid, targets));
}
CsrEdges::from_adjacency(adjacency, coords)
}
}
impl Default for DeltaEdgeSlab {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct CsrMutableEdges {
base: CsrEdges,
delta: DeltaEdgeSlab,
coords: Vec<AbsCoord>,
vertex_ids: Vec<u32>,
batch_mode: bool,
}
impl CsrMutableEdges {
pub fn new() -> Self {
Self {
base: CsrEdges::empty(),
delta: DeltaEdgeSlab::new(),
coords: Vec::new(),
vertex_ids: Vec::new(),
batch_mode: false,
}
}
pub fn with_coords(coords: Vec<AbsCoord>) -> Self {
let num_vertices = coords.len();
let vertex_ids: Vec<u32> = (0..num_vertices as u32).collect();
let adjacency: Vec<_> = vertex_ids.iter().map(|&id| (id, Vec::new())).collect();
Self {
base: CsrEdges::from_adjacency(adjacency, &coords),
delta: DeltaEdgeSlab::new(),
coords,
vertex_ids,
batch_mode: false,
}
}
pub fn add_edge(&mut self, from: VertexId, to: VertexId) {
self.delta.add_edge(from, to);
self.maybe_rebuild();
}
pub fn remove_edge(&mut self, from: VertexId, to: VertexId) {
self.delta.remove_edge(from, to);
self.maybe_rebuild();
}
pub fn out_edges(&self, v: VertexId) -> Vec<VertexId> {
if self.delta.op_count() == 0 {
self.base.out_edges(v).to_vec()
} else {
self.delta.merged_view(&self.base, v)
}
}
#[inline]
pub fn out_edges_ref(&self, v: VertexId) -> Option<&[VertexId]> {
if self.delta.op_count() == 0 {
Some(self.base.out_edges(v))
} else {
None
}
}
pub fn in_edges(&self, v: VertexId) -> &[VertexId] {
self.base.in_edges(v)
}
#[inline]
pub fn in_edges_ref(&self, v: VertexId) -> Option<&[VertexId]> {
if self.delta.op_count() == 0 {
Some(self.base.in_edges(v))
} else {
None
}
}
pub fn delta_size(&self) -> usize {
self.delta.op_count()
}
pub fn rebuild(&mut self) {
if self.delta.op_count() > 0 || self.delta.needs_rebuild() {
self.base = self
.delta
.apply_to_csr(&self.base, &self.coords, &self.vertex_ids);
self.delta.clear();
}
}
fn maybe_rebuild(&mut self) {
if !self.batch_mode && self.delta.needs_rebuild() {
self.rebuild();
}
}
pub fn begin_batch(&mut self) {
self.batch_mode = true;
}
pub fn end_batch(&mut self) {
self.batch_mode = false;
if self.delta.op_count() > 0 || self.delta.needs_rebuild() {
self.rebuild();
}
}
pub fn add_vertex(&mut self, coord: AbsCoord, vertex_id: u32) -> usize {
let idx = self.coords.len();
self.coords.push(coord);
self.vertex_ids.push(vertex_id);
self.rebuild();
idx
}
pub fn add_vertices_batch(&mut self, items: &[(AbsCoord, u32)]) {
if items.is_empty() {
return;
}
let start_len = self.coords.len();
self.coords.reserve(items.len());
self.vertex_ids.reserve(items.len());
for (coord, vid) in items {
self.coords.push(*coord);
self.vertex_ids.push(*vid);
}
self.rebuild();
debug_assert_eq!(self.coords.len(), start_len + items.len());
}
pub fn update_coord(&mut self, vertex_id: VertexId, new_coord: AbsCoord) {
if let Some(pos) = self.vertex_ids.iter().position(|&id| id == vertex_id.0) {
self.coords[pos] = new_coord;
self.delta.mark_dirty();
}
}
pub fn build_from_adjacency(
&mut self,
adjacency: Vec<(u32, Vec<u32>)>,
coords: Vec<AbsCoord>,
vertex_ids: Vec<u32>,
) {
self.base = CsrEdges::from_adjacency(adjacency, &coords);
self.coords = coords;
self.vertex_ids = vertex_ids;
self.delta.clear();
}
}
impl Default for CsrMutableEdges {
fn default() -> Self {
Self::new()
}
}