pub mod build;
pub mod checkpoint;
pub mod errors;
pub mod interruptible;
pub mod merge;
pub mod scheduler;
pub mod swap;
pub use build::{
BuildStats, CompactionSnapshot, build_compacted_csr, build_csr_from_edges, snapshot_edges,
};
pub use checkpoint::{
CheckpointStats, CompactionCheckpoint, CounterCheckpoint, EdgeStoreCheckpoint,
};
pub use errors::{
BuildFailureReason, CompactionError, CompactionPhase, ComponentState, CounterReconcileState,
Direction, InterruptReason, PostErrorState, SwapFailureReason, SwapPreconditionError,
SwapPreconditions,
};
pub use interruptible::{
CancellationToken, CompactionProgress, DEFAULT_CHUNK_SIZE, InterruptibleConfig,
InterruptibleResult, InterruptibleStats, InterruptibleStatsSnapshot, compact_interruptible,
};
pub use merge::{MergeStats, MergedEdge, merge_delta_edges};
pub use scheduler::{CompactionScheduler, CompactionThresholds, CompactionTrigger, SchedulerStats};
pub use swap::{SwapInput, SwapResult, swap_bidirectional_csr, swap_single_csr};
use crate::graph::unified::concurrent::CodeGraph;
pub fn compact_edges_in_place(graph: &CodeGraph) -> Result<(), CompactionError> {
let node_count = graph.node_count();
let forward_snapshot = {
let forward_store = graph.edges().forward();
snapshot_edges(&forward_store, node_count)
};
let reverse_snapshot = {
let reverse_store = graph.edges().reverse();
snapshot_edges(&reverse_store, node_count)
};
let (forward_result, reverse_result) = rayon::join(
|| build_compacted_csr(&forward_snapshot, Direction::Forward),
|| build_compacted_csr(&reverse_snapshot, Direction::Reverse),
);
let (forward_csr, _forward_stats) = forward_result?;
let (reverse_csr, _reverse_stats) = reverse_result?;
graph
.edges()
.swap_csrs_and_clear_deltas(forward_csr, reverse_csr);
Ok(())
}
#[cfg(test)]
mod compact_in_place_tests {
use super::compact_edges_in_place;
use crate::graph::unified::concurrent::CodeGraph;
use crate::graph::unified::edge::kind::{EdgeKind, ResolvedVia};
use crate::graph::unified::node::id::NodeId;
use crate::graph::unified::node::kind::NodeKind;
use crate::graph::unified::storage::arena::NodeEntry;
use std::path::Path;
fn alloc_fn(g: &mut CodeGraph, name: &str) -> NodeId {
let file = g.files_mut().register(Path::new("lib.rs")).unwrap();
let n = g.strings_mut().intern(name).unwrap();
g.nodes_mut()
.alloc(NodeEntry::new(NodeKind::Function, n, file).with_qualified_name(n))
.unwrap()
}
fn calls(g: &mut CodeGraph, s: NodeId, t: NodeId) {
let file = g.nodes().get(s).unwrap().file;
g.edges_mut().add_edge(
s,
t,
EdgeKind::Calls {
argument_count: 0,
is_async: false,
resolved_via: ResolvedVia::Direct,
},
file,
);
}
fn sorted_live_edges(g: &CodeGraph) -> Vec<(u32, u32)> {
let mut e: Vec<(u32, u32)> = g
.edges()
.all_live_forward_edges()
.iter()
.map(|x| (x.source.index(), x.target.index()))
.collect();
e.sort_unstable();
e
}
#[test]
fn compact_edges_in_place_moves_delta_to_csr_preserving_live_edges() {
let mut g = CodeGraph::new();
let a = alloc_fn(&mut g, "a");
let b = alloc_fn(&mut g, "b");
let c = alloc_fn(&mut g, "c");
calls(&mut g, a, b);
calls(&mut g, a, c);
calls(&mut g, b, c);
calls(&mut g, c, c);
assert!(
g.edges().forward().csr().is_none(),
"expected delta-backed before compaction"
);
assert!(g.edges().stats().forward.delta_edge_count > 0);
let before = sorted_live_edges(&g);
assert_eq!(before.len(), 4);
compact_edges_in_place(&g).unwrap();
assert!(
g.edges().forward().csr().is_some(),
"forward must be CSR-backed after compaction"
);
assert!(
g.edges().reverse().csr().is_some(),
"reverse must be compacted too"
);
assert_eq!(g.edges().stats().forward.delta_edge_count, 0);
assert_eq!(g.edges().stats().reverse.delta_edge_count, 0);
assert_eq!(
sorted_live_edges(&g),
before,
"live edges must be preserved by compaction"
);
compact_edges_in_place(&g).unwrap();
assert!(g.edges().forward().csr().is_some());
assert_eq!(g.edges().stats().forward.delta_edge_count, 0);
assert_eq!(
sorted_live_edges(&g),
before,
"idempotent compaction preserves edges"
);
}
}