Get or create a dense ID for a node. Returns `Err(GraphError::NodeOverflow)` when the partition already holds `MAX_NODES_PER_CSR` nodes and a new name is introduced. The check uses a typed `Result` rather than `debug_assert!` so the failure mode is loud and deterministic — a silent `u32` wrap would reproduce the same class of bug as the label-overflow issue this crate fixed previously.
(&mut self, node: &str)
| 15 | /// and deterministic — a silent `u32` wrap would reproduce the same class |
| 16 | /// of bug as the label-overflow issue this crate fixed previously. |
| 17 | pub(crate) fn ensure_node(&mut self, node: &str) -> Result<u32, crate::GraphError> { |
| 18 | match self.node_to_id.entry(node.to_string()) { |
| 19 | Entry::Occupied(e) => Ok(*e.get()), |
| 20 | Entry::Vacant(e) => { |
| 21 | let len = self.id_to_node.len(); |
| 22 | if len >= crate::MAX_NODES_PER_CSR { |
| 23 | return Err(crate::GraphError::NodeOverflow { used: len }); |
| 24 | } |
| 25 | let id = len as u32; |
| 26 | e.insert(id); |
| 27 | self.id_to_node.push(node.to_string()); |
| 28 | // Extend dense offsets (new node has 0 edges in dense part). |
| 29 | self.out_offsets |
| 30 | .push(*self.out_offsets.last().unwrap_or(&0)); |
| 31 | self.in_offsets.push(*self.in_offsets.last().unwrap_or(&0)); |
| 32 | // Extend buffer and access tracking. |
| 33 | self.buffer_out.push(Vec::new()); |
| 34 | self.buffer_in.push(Vec::new()); |
| 35 | self.buffer_out_weights.push(Vec::new()); |
| 36 | self.buffer_in_weights.push(Vec::new()); |
| 37 | self.node_label_bits.push(0); |
| 38 | // Surrogate is populated later by the EdgePut handler; start |
| 39 | // with the ZERO sentinel so unset nodes are never in a bitmap. |
| 40 | self.node_surrogates.push(0); |
| 41 | self.access_counts.push(std::cell::Cell::new(0)); |
| 42 | Ok(id) |
| 43 | } |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | /// Get or create a dense ID for a label. |
| 48 | /// |