Output the content of an alive graph to a span buffer. This function traverses the graph in SCC order (as computed by [`compute_order`](crate::output::alive::compute_order)) and writes each span's content to the buffer. Conflicts are handled by: - **Cyclic conflicts**: Multi-span SCCs are output with conflict markers - **Zombie content**: Deleted vertices with live edges get zombie markers # Ar
(
changes: &C,
hash_fn: F,
graph: &AliveGraph,
order: &OrderResult,
buffer: &mut V,
)
| 135 | /// 3. For zombie vertices: wrap in zombie conflict markers |
| 136 | /// 4. Track and report any content retrieval errors |
| 137 | pub fn output_graph_content<C, F, V>( |
| 138 | changes: &C, |
| 139 | hash_fn: F, |
| 140 | graph: &AliveGraph, |
| 141 | order: &OrderResult, |
| 142 | buffer: &mut V, |
| 143 | ) -> OutputResult<()> |
| 144 | where |
| 145 | C: ChangeStore, |
| 146 | F: Fn(NodeId) -> Option<Hash>, |
| 147 | V: VertexBuffer, |
| 148 | { |
| 149 | // Track conflict IDs |
| 150 | let mut conflict_id: usize = 0; |
| 151 | |
| 152 | // Track zombie state |
| 153 | let mut in_zombie: Option<usize> = None; |
| 154 | |
| 155 | // Process SCCs in reverse order (Tarjan produces reverse topological order, |
| 156 | // so we iterate in reverse to get forward topological order for correct output) |
| 157 | for scc in order.sccs.iter().rev() { |
| 158 | // Skip empty SCCs (shouldn't happen, but be safe) |
| 159 | if scc.is_empty() { |
| 160 | continue; |
| 161 | } |
| 162 | |
| 163 | // Check if this is a cyclic conflict (multi-span SCC) |
| 164 | let is_cyclic = scc.len() > 1; |
| 165 | |
| 166 | if is_cyclic { |
| 167 | conflict_id += 1; |
| 168 | buffer |
| 169 | .begin_cyclic_conflict(conflict_id) |
| 170 | .map_err(OutputError::io)?; |
| 171 | } |
| 172 | |
| 173 | // Output each span in the SCC |
| 174 | for (i, &vertex_id) in scc.iter().enumerate() { |
| 175 | // Get span data |
| 176 | let vertex_data = match graph.try_get_vertex(vertex_id) { |
| 177 | Some(v) => v, |
| 178 | None => continue, |
| 179 | }; |
| 180 | |
| 181 | let node = vertex_data.node; |
| 182 | |
| 183 | // Handle zombie state transitions |
| 184 | let is_zombie = vertex_data.is_zombie(); |
| 185 | |
| 186 | if is_zombie && in_zombie.is_none() { |
| 187 | // Entering zombie region |
| 188 | conflict_id += 1; |
| 189 | in_zombie = Some(conflict_id); |
| 190 | |
| 191 | let hash = hash_fn(node.change); |
| 192 | let hashes: Vec<Hash> = hash.into_iter().collect(); |
| 193 | let hashes_ref: Option<&[Hash]> = if hashes.is_empty() { |
| 194 | None |