diff --git a/Cargo.lock b/Cargo.lock index 2440605..d1fe415 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -683,6 +683,7 @@ dependencies = [ "fastanvil", "fastnbt", "flate2", + "indexmap 2.0.0", "indicatif", "itertools", "mimalloc", diff --git a/Cargo.toml b/Cargo.toml index a0e6699..28b85f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ eyre = "0.6.8" fastnbt = "2" fastanvil = "0.26" indicatif = { version = "0.17.9", features = ["rayon"] } +indexmap = "2.0.0" itertools = "0.11.0" petgraph = "0.6.3" quine-mc_cluskey = "0.2.4" diff --git a/src/bin/check_nbt_world_cycle.rs b/src/bin/check_nbt_world_cycle.rs index 5e103e8..ca99d5e 100644 --- a/src/bin/check_nbt_world_cycle.rs +++ b/src/bin/check_nbt_world_cycle.rs @@ -63,12 +63,13 @@ fn main() -> eyre::Result<()> { .map(|node| (node.id, node.kind.name())) .collect::>(); - let cyclic_components = kosaraju_scc(&graph.to_petgraph_only_edges()) + let petgraph = graph.to_petgraph_only_edges(); + let cyclic_components = kosaraju_scc(&petgraph) .into_iter() .map(|component| { component .into_iter() - .map(|node| node.index()) + .map(|node| petgraph[node]) .filter(|id| world_graph.positions.contains_key(id)) .collect::>() }) diff --git a/src/graph/graphviz.rs b/src/graph/graphviz.rs index 9989443..d0a161b 100644 --- a/src/graph/graphviz.rs +++ b/src/graph/graphviz.rs @@ -6,7 +6,7 @@ use super::cluster::ClusteredGraph; use super::logic::LogicGraph; use super::module::{GraphModule, GraphWithSubGraphs}; use super::world::WorldGraph; -use super::{Graph, GraphNode, GraphNodeId, SubGraph}; +use super::{Graph, GraphNodeId, GraphNodeRef, SubGraph}; use crate::graph::module::GraphModulePortTarget; pub struct GraphvizBuilder<'a> { @@ -135,7 +135,7 @@ digraph {graph_name} {{ } } - fn print_node(&self, node: &GraphNode) -> String { + fn print_node(&self, node: GraphNodeRef<'_>) -> String { let name = if self.show_node_id { format!("{} #{}", node.kind.name(), node.id) } else { @@ -268,10 +268,14 @@ digraph {graph_name} {{ .nodes .iter() .flat_map(|node| { - node.inputs.iter().map(|input| { - let in_suffix = format!("in{}_{}", input, node.id); - format!(" node{}:out->node{}:{}\n", input, node.id, in_suffix) - }) + let node_id = node.id; + node.inputs + .iter() + .map(move |input| { + let in_suffix = format!("in{}_{}", input, node_id); + format!(" node{}:out->node{}:{}\n", input, node_id, in_suffix) + }) + .collect_vec() }) .collect::>() .join("") @@ -521,15 +525,11 @@ mod tests { #[test] fn table_graphviz_shows_node_tags() { - let graph = Graph { - nodes: vec![GraphNode { - id: 0, - kind: GraphNodeKind::None, - tag: "Folded RS latch feedback SCC [1, 2, 3, 4]".to_owned(), - ..Default::default() - }], + let graph = Graph::from_nodes(vec![GraphNode { + kind: GraphNodeKind::None, + tag: "Folded RS latch feedback SCC [1, 2, 3, 4]".to_owned(), ..Default::default() - }; + }]); let dot = GraphvizBuilder::default() .with_graph(&graph) @@ -541,15 +541,11 @@ mod tests { #[test] fn graphviz_can_hide_node_tags() { - let graph = Graph { - nodes: vec![GraphNode { - id: 0, - kind: GraphNodeKind::None, - tag: "debug source tag".to_owned(), - ..Default::default() - }], + let graph = Graph::from_nodes(vec![GraphNode { + kind: GraphNodeKind::None, + tag: "debug source tag".to_owned(), ..Default::default() - }; + }]); let dot = GraphvizBuilder::default() .with_graph(&graph) diff --git a/src/graph/logic.rs b/src/graph/logic.rs index 2ea0f55..ff4c5c3 100644 --- a/src/graph/logic.rs +++ b/src/graph/logic.rs @@ -98,22 +98,18 @@ impl LogicGraph { where I: IntoIterator, { - let mut next_id = self.graph.max_node_id().map_or(0, |id| id + 1); for (name, source_id) in outputs { if self.find_node_by_id(source_id).is_none() { eyre::bail!("cannot attach output {name}: missing source node {source_id}"); } - self.graph.nodes.push(GraphNode { - id: next_id, + self.graph.add_node(GraphNode { kind: GraphNodeKind::Output(name), inputs: vec![source_id], ..Default::default() }); - next_id += 1; } - self.graph.nodes.sort_by_key(|node| node.id); self.graph.build_outputs(); self.graph.build_producers(); self.graph.build_consumers(); @@ -140,7 +136,7 @@ impl LogicGraph { .filter_map(|node| match &node.kind { GraphNodeKind::Output(name) => output_source_ids .contains(&node.inputs[0]) - .then_some(name.as_str()), + .then_some(name.clone()), _ => None, }) .collect::>(); @@ -150,6 +146,7 @@ impl LogicGraph { } output_names.sort(); + let output_names = output_names.iter().map(String::as_str).collect_vec(); table.select_outputs(&output_names) } } @@ -310,9 +307,8 @@ fn permuted_output_table_set( #[derive(Default)] pub struct LogicGraphBuilder { stmt: String, - node_id: usize, ptr: usize, - nodes: Vec, + graph: Graph, inputs: HashMap, } @@ -347,19 +343,9 @@ impl LogicGraphBuilder { pub fn build(mut self, output_name: String) -> eyre::Result { self.do_parse(output_name); - let mut graph = Graph { - nodes: self.nodes.clone(), - ..Default::default() - }; - graph.build_outputs(); - - Ok(LogicGraph { graph }) - } + self.graph.build_outputs(); - fn next_id(&mut self) -> usize { - let id = self.node_id; - self.node_id += 1; - id + Ok(LogicGraph { graph: self.graph }) } fn next_ptr(&mut self) -> usize { @@ -369,14 +355,11 @@ impl LogicGraphBuilder { } fn new_node(&mut self, kind: GraphNodeKind, inputs: Vec) -> GraphNodeId { - let node = GraphNode { - id: self.next_id(), + self.graph.add_node(GraphNode { kind, inputs, ..Default::default() - }; - self.nodes.push(node); - self.nodes.last().unwrap().clone().id + }) } fn new_input_node(&mut self, name: String) -> GraphNodeId { @@ -780,8 +763,7 @@ mod tests { let splits = finish.graph.split_with_outputs(); - let mut graph: Graph = (&finish.graph.split_with_outputs()[0]).into(); - graph = graph.rebuild_node_ids(); + let graph: Graph = (&finish.graph.split_with_outputs()[0]).into(); println!("{}", graph.to_graphviz()); let mut transform = LogicGraphTransformer::new(LogicGraph { graph }); @@ -792,8 +774,7 @@ mod tests { #[allow(clippy::needless_range_loop)] for index in 1..2 { - let mut graph: Graph = (&splits[index]).into(); - graph = graph.rebuild_node_ids(); + let graph: Graph = (&splits[index]).into(); println!("{}", graph.to_graphviz()); let mut transform = LogicGraphTransformer::new(LogicGraph { graph }); diff --git a/src/graph/mod.rs b/src/graph/mod.rs index 082203a..18c45d4 100644 --- a/src/graph/mod.rs +++ b/src/graph/mod.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet, VecDeque}; use std::fmt::Display; +use indexmap::IndexMap; use itertools::Itertools; use module::GraphModuleBuilder; use petgraph::stable_graph::NodeIndex; @@ -115,7 +116,6 @@ impl GraphNodeKind { #[derive(Default, Debug, Clone)] pub struct GraphNode { - pub id: GraphNodeId, pub kind: GraphNodeKind, pub inputs: Vec, pub outputs: Vec, @@ -126,8 +126,7 @@ impl Display for GraphNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "N{} {:?} := {} {:?} {}", - self.id, + "{:?} := {} {:?} {}", self.outputs, self.kind.name(), self.inputs, @@ -137,32 +136,245 @@ impl Display for GraphNode { } impl GraphNode { - pub fn new(id: GraphNodeId, kind: GraphNodeKind) -> Self { + pub fn new(kind: GraphNodeKind) -> Self { Self { - id, kind, ..Default::default() } } } +#[derive(Clone, Copy)] +pub struct GraphNodeRef<'a> { + pub id: GraphNodeId, + node: &'a GraphNode, +} + +impl GraphNodeRef<'_> { + pub fn new(id: GraphNodeId, node: &GraphNode) -> GraphNodeRef<'_> { + GraphNodeRef { id, node } + } + + pub fn clone_node(&self) -> GraphNode { + self.node.clone() + } +} + +impl std::ops::Deref for GraphNodeRef<'_> { + type Target = GraphNode; + + fn deref(&self) -> &Self::Target { + self.node + } +} + +impl Display for GraphNodeRef<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(self.node, f) + } +} + +pub struct GraphNodeMut<'a> { + pub id: GraphNodeId, + node: &'a mut GraphNode, +} + +impl std::ops::Deref for GraphNodeMut<'_> { + type Target = GraphNode; + + fn deref(&self) -> &Self::Target { + self.node + } +} + +impl std::ops::DerefMut for GraphNodeMut<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.node + } +} + +#[derive(Debug, Clone)] +pub struct GraphNodeEntry { + pub id: GraphNodeId, + pub node: GraphNode, +} + +impl std::ops::Deref for GraphNodeEntry { + type Target = GraphNode; + + fn deref(&self) -> &Self::Target { + &self.node + } +} + +impl std::ops::DerefMut for GraphNodeEntry { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.node + } +} + +#[derive(Default, Debug, Clone)] +pub struct GraphNodes { + nodes: IndexMap, + next_node_id: GraphNodeId, +} + +impl GraphNodes { + pub fn len(&self) -> usize { + self.nodes.len() + } + + pub fn is_empty(&self) -> bool { + self.nodes.is_empty() + } + + pub fn get(&self, id: GraphNodeId) -> Option> { + self.nodes.get(&id).map(|node| GraphNodeRef { id, node }) + } + + pub fn get_mut(&mut self, id: GraphNodeId) -> Option> { + self.nodes + .get_mut(&id) + .map(|node| GraphNodeMut { id, node }) + } + + pub fn iter(&self) -> impl Iterator> { + self.nodes + .iter() + .map(|(&id, node)| GraphNodeRef { id, node }) + } + + pub fn iter_mut(&mut self) -> impl Iterator> { + self.nodes + .iter_mut() + .map(|(&id, node)| GraphNodeMut { id, node }) + } + + pub fn retain(&mut self, f: impl FnMut(&GraphNode) -> bool) { + let mut f = f; + self.nodes.retain(|_, node| f(node)); + } + + pub fn remove(&mut self, index: usize) -> GraphNode { + self.remove_entry(index).node + } + + pub fn remove_entry(&mut self, index: usize) -> GraphNodeEntry { + self.nodes + .shift_remove_index(index) + .map(|(id, node)| GraphNodeEntry { id, node }) + .expect("node index must exist") + } + + fn insert_with_id(&mut self, id: GraphNodeId, node: GraphNode) { + self.next_node_id = self.next_node_id.max(id + 1); + self.nodes.insert(id, node); + } + + fn next_node_id(&self) -> GraphNodeId { + self.next_node_id + } + + fn allocate_node_id(&mut self) -> GraphNodeId { + let id = self.next_node_id(); + self.next_node_id = id + 1; + id + } + + fn add(&mut self, node: GraphNode) -> GraphNodeId { + let id = self.allocate_node_id(); + self.insert_with_id(id, node); + id + } +} + +impl From> for GraphNodes { + fn from(mut nodes: Vec<(GraphNodeId, GraphNode)>) -> Self { + let next_node_id = nodes.iter().map(|(id, _)| *id).max().map_or(0, |id| id + 1); + nodes.sort_by_key(|(id, _)| *id); + Self { + nodes: nodes.into_iter().collect(), + next_node_id, + } + } +} + +impl IntoIterator for GraphNodes { + type Item = GraphNodeEntry; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.nodes + .into_iter() + .map(|(id, node)| GraphNodeEntry { id, node }) + .collect_vec() + .into_iter() + } +} + +impl<'a> IntoIterator for &'a GraphNodes { + type Item = GraphNodeRef<'a>; + type IntoIter = std::vec::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { + self.iter().collect_vec().into_iter() + } +} + +impl<'a> IntoIterator for &'a mut GraphNodes { + type Item = GraphNodeMut<'a>; + type IntoIter = std::vec::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut().collect_vec().into_iter() + } +} + #[derive(Default, Debug, Clone)] pub struct Graph { - pub nodes: Vec, + pub nodes: GraphNodes, pub producers: HashMap>, pub consumers: HashMap>, } impl Graph { - pub fn to_petgraph_only_edges(&self) -> petgraph::Graph<(), ()> { - let edges = self.nodes.iter().flat_map(|node| { - node.outputs - .iter() - .map(|&id| (NodeIndex::new(node.id), NodeIndex::new(id))) - .collect_vec() - }); + pub fn from_nodes_with_ids(nodes: Vec<(GraphNodeId, GraphNode)>) -> Self { + Self { + nodes: nodes.into(), + ..Default::default() + } + } + + pub fn from_nodes(nodes: Vec) -> Self { + let nodes = nodes.into_iter().enumerate().collect_vec(); + Self::from_nodes_with_ids(nodes) + } + + fn to_petgraph_with_node_ids(&self) -> petgraph::Graph { + let mut graph = petgraph::Graph::::new(); + let mut id_to_index = HashMap::new(); + + for node in &self.nodes { + let index = graph.add_node(node.id); + id_to_index.insert(node.id, index); + } + + for node in &self.nodes { + let Some(source) = id_to_index.get(&node.id).copied() else { + continue; + }; + for output in &node.outputs { + if let Some(target) = id_to_index.get(output).copied() { + graph.add_edge(source, target, ()); + } + } + } + + graph + } - petgraph::Graph::<(), ()>::from_edges(edges) + pub fn to_petgraph_only_edges(&self) -> petgraph::Graph { + self.to_petgraph_with_node_ids() } pub fn to_petgraph(&self) -> petgraph::Graph { @@ -184,25 +396,24 @@ impl Graph { } pub fn topological_order(&self) -> Vec { - let nodes: HashSet = self.nodes.iter().map(|node| node.id).collect(); - petgraph::algo::toposort(&self.to_petgraph_only_edges(), None) + let graph = self.to_petgraph_only_edges(); + petgraph::algo::toposort(&graph, None) .unwrap() .iter() - .map(|index| index.index()) - .filter(|id| nodes.contains(id)) + .map(|index| graph[*index]) .collect_vec() } pub fn strongly_connected_components(&self) -> Vec> { let node_ids: HashSet = self.nodes.iter().map(|node| node.id).collect(); + let graph = self.to_petgraph_only_edges(); let mut seen = HashSet::new(); - let mut components = petgraph::algo::kosaraju_scc(&self.to_petgraph_only_edges()) + let mut components = petgraph::algo::kosaraju_scc(&graph) .into_iter() .filter_map(|component| { let mut component = component .into_iter() - .map(|index| index.index()) - .filter(|id| node_ids.contains(id)) + .map(|index| graph[index]) .collect_vec(); component.sort(); if component.is_empty() { @@ -229,17 +440,16 @@ impl Graph { let mut edges = nodes .iter() .flat_map(|node_id| { - let node = self.find_node_by_id(*node_id).into_iter(); - node.flat_map(move |node| { - if incoming { - node.inputs.iter() - } else { - node.outputs.iter() - } - }) + self.find_node_by_id(*node_id) + .map_or_else(Vec::new, |node| { + if incoming { + node.inputs.clone() + } else { + node.outputs.clone() + } + }) }) .filter(|node_id| !nodes.contains(node_id)) - .copied() .collect::>(); edges.sort(); edges.dedup(); @@ -250,10 +460,12 @@ impl Graph { &self, target_root: GraphNodeId, ) -> petgraph::algo::dominators::Dominators { - petgraph::algo::dominators::simple_fast( - &self.to_petgraph_only_edges(), - NodeIndex::new(target_root), - ) + let graph = self.to_petgraph_only_edges(); + let target_root = graph + .node_indices() + .find(|index| graph[*index] == target_root) + .expect("target root must exist in graph"); + petgraph::algo::dominators::simple_fast(&graph, target_root) } pub fn inputs(&self) -> Vec { @@ -279,22 +491,14 @@ impl Graph { self.nodes.iter().map(|node| node.id).collect() } - pub fn concat(&mut self, mut other: Self) { - if let Some(id) = self.max_node_id() { - other.rebuild_node_id_base(id + 1); - } - - self.nodes.extend(other.nodes); - self.producers.extend(other.producers); - self.consumers.extend(other.consumers); + pub fn concat(&mut self, other: Self) { + self.append_graph_with_replacements(other, &HashMap::new()); + self.build_producers(); + self.build_consumers(); } // src와 other의 input이 같은 것 끼리 연결하여 merge함 - pub fn merge_by_input(&mut self, mut other: Self) { - if let Some(id) = self.max_node_id() { - other.rebuild_node_id_base(id + 1); - } - + pub fn merge_by_input(&mut self, other: Self) { let src_inputs: HashMap = self .nodes .iter() @@ -304,44 +508,43 @@ impl Graph { }) .collect(); - let replace_targets: Vec<(GraphNodeId, GraphNodeId)> = other + let shared_inputs: Vec<(GraphNodeId, GraphNodeId, Vec)> = other .nodes .iter() .filter_map(|node| match &node.kind { - GraphNodeKind::Input(name) if src_inputs.contains_key(name) => { - Some((node.id, *src_inputs.get(name).unwrap())) - } + GraphNodeKind::Input(name) if src_inputs.contains_key(name) => Some(( + node.id, + *src_inputs.get(name).unwrap(), + node.outputs.clone(), + )), _ => None, }) .collect(); + let old_to_existing_ids = shared_inputs + .iter() + .map(|(from, to, _)| (*from, *to)) + .collect::>(); - for (from, to) in replace_targets { - let node = other.nodes.iter_mut().find(|node| node.id == from).unwrap(); + let old_to_current_ids = self.append_graph_with_replacements(other, &old_to_existing_ids); + for (_, to, outputs) in shared_inputs { + let outputs = outputs + .iter() + .map(|id| old_to_current_ids.get(id).copied().unwrap_or(*id)) + .collect_vec(); self.find_node_by_id_mut(to) .unwrap() .outputs - .extend(node.outputs.clone()); - node.id = to; + .extend(outputs); } - other.build_producers(); - other.build_consumers(); - - self.nodes - .extend(other.nodes.into_iter().filter(|node| - !matches!(&node.kind, GraphNodeKind::Input(name) if src_inputs.contains_key(name)))); self.build_inputs(); self.build_producers(); self.build_consumers(); } // src의 output을 target의 input과 연결하여 merge함 - pub fn merge_by_outin(&mut self, mut target: Self, out_in: Vec<(&str, &str)>) { - if let Some(id) = self.max_node_id() { - target.rebuild_node_id_base(id + 1); - } - + pub fn merge_by_outin(&mut self, target: Self, out_in: Vec<(&str, &str)>) { let outputs: HashSet<_> = out_in.iter().map(|(output, _)| *output).collect(); let inputs: HashSet<_> = out_in.iter().map(|(_, input)| *input).collect(); @@ -356,52 +559,52 @@ impl Graph { }) .collect(); - let target_inputs: HashMap> = target + let target_inputs: HashMap)> = target .nodes .iter() .filter_map(|node| match &node.kind { GraphNodeKind::Input(name) if inputs.contains(name.as_str()) => { - Some((name.to_owned(), node.outputs.clone())) + Some((name.to_owned(), (node.id, node.outputs.clone()))) } _ => None, }) .collect(); + let old_to_existing_ids = out_in + .iter() + .map(|(output, input)| { + let (target_input_id, _) = &target_inputs[*input]; + (*target_input_id, src_outputs[*output]) + }) + .collect::>(); + + self.nodes.retain(|node| + !matches!(&node.kind, GraphNodeKind::Output(name) if src_outputs.contains_key(name.as_str()))); + + let old_to_current_ids = self.append_graph_with_replacements(target, &old_to_existing_ids); for (output, input) in out_in { let out_node = src_outputs[output]; - let in_node = target_inputs[input].clone(); - - self.find_node_by_id_mut(out_node).unwrap().outputs = in_node.clone(); + let (_, target_input_outputs) = &target_inputs[input]; + let in_node = target_input_outputs + .iter() + .map(|id| old_to_current_ids.get(id).copied().unwrap_or(*id)) + .collect_vec(); - for input in in_node { - target.find_node_by_id_mut(input).unwrap().inputs = vec![out_node]; - } + self.find_node_by_id_mut(out_node).unwrap().outputs = in_node; } - target.build_producers(); - target.build_consumers(); - - self.nodes.retain(|node| - !matches!(&node.kind, GraphNodeKind::Output(name) if src_outputs.contains_key(name.as_str()))); - self.nodes - .extend(target.nodes.into_iter().filter(|node| - !matches!(&node.kind, GraphNodeKind::Input(name) if target_inputs.contains_key(name.as_str())))); self.build_inputs(); self.build_producers(); self.build_consumers(); } // self의 input, output들을 target의 input과 연결함 - pub fn merge(&mut self, mut target: Self) { - if let Some(id) = self.max_node_id() { - target.rebuild_node_id_base(id + 1); - } - + pub fn merge(&mut self, target: Self) { let target_inputs: HashSet<_> = target .nodes .iter() .filter_map(|node| match &node.kind { - GraphNodeKind::Input(name) | GraphNodeKind::Output(name) => Some(name.as_str()), + GraphNodeKind::Input(name) | GraphNodeKind::Output(name) => Some(name.to_owned()), _ => None, }) .collect(); @@ -413,7 +616,7 @@ impl Graph { GraphNodeKind::Input(name) | GraphNodeKind::Output(name) => Some(name.to_owned()), _ => None, }) - .filter(|name| target_inputs.contains(name.as_str())) + .filter(|name| target_inputs.contains(name)) .collect(); let src_inputs: HashMap = self @@ -438,19 +641,35 @@ impl Graph { }) .collect(); - let target_inputs: HashMap> = target + let target_inputs: HashMap)> = target .nodes .iter() .filter_map(|node| match &node.kind { GraphNodeKind::Input(name) if targets.contains(name.as_str()) => { - Some((name.to_owned(), node.outputs.clone())) + Some((name.to_owned(), (node.id, node.outputs.clone()))) } _ => None, }) .collect(); + let mut old_to_existing_ids = HashMap::new(); for name in &targets { - let tar_in = target_inputs[name].clone(); + let (target_input_id, _) = &target_inputs[name]; + if let Some(src_in) = src_inputs.get(name) { + old_to_existing_ids.insert(*target_input_id, *src_in); + } else if let Some(src_out) = src_outputs.get(name) { + old_to_existing_ids.insert(*target_input_id, *src_out); + } + } + + let old_to_current_ids = self.append_graph_with_replacements(target, &old_to_existing_ids); + + for name in &targets { + let (_, target_input_outputs) = &target_inputs[name]; + let tar_in = target_input_outputs + .iter() + .map(|id| old_to_current_ids.get(id).copied().unwrap_or(*id)) + .collect_vec(); if let Some(src_in) = src_inputs.get(name) { self.find_node_by_id_mut(*src_in) @@ -461,27 +680,17 @@ impl Graph { self.find_node_by_id_mut(*src_out) .unwrap() .outputs - .extend(tar_in.clone()); - - for input in tar_in { - target.find_node_by_id_mut(input).unwrap().inputs = vec![*src_out]; - } + .extend(tar_in); } } - target.build_producers(); - target.build_consumers(); - - self.nodes - .extend(target.nodes.into_iter().filter(|node| - !matches!(&node.kind, GraphNodeKind::Input(name) if target_inputs.contains_key(name.as_str())))); self.build_inputs(); self.build_producers(); self.build_consumers(); } pub fn replace_input_name(&mut self, from: &str, to: String) -> bool { - if let Some(node) = self + if let Some(mut node) = self .nodes .iter_mut() .find(|node| matches!(&node.kind, GraphNodeKind::Input(name) if name == from)) @@ -494,7 +703,7 @@ impl Graph { } pub fn replace_output_name(&mut self, from: &str, to: String) -> bool { - if let Some(node) = self + if let Some(mut node) = self .nodes .iter_mut() .find(|node| matches!(&node.kind, GraphNodeKind::Output(name) if name == from)) @@ -510,126 +719,72 @@ impl Graph { petgraph::algo::is_cyclic_directed(&self.to_petgraph_only_edges()) } - pub fn max_node_id(&self) -> Option { - self.nodes - .iter() - .max_by_key(|node| node.id) - .map(|node| node.id) + pub fn add_node(&mut self, node: GraphNode) -> GraphNodeId { + self.nodes.add(node) } - pub fn find_node_by_input_name(&mut self, input_name: &str) -> Option<&mut GraphNode> { + /// Appends `imported_graph` into this graph using fresh ids for every imported node. + /// Old ids in `old_to_existing_ids` are not inserted or overwritten; references to + /// those imported nodes are resolved to the existing node ids in this graph. + pub(crate) fn append_graph_with_replacements( + &mut self, + imported_graph: Self, + old_to_existing_ids: &HashMap, + ) -> HashMap { + let nodes = imported_graph.nodes.into_iter().collect_vec(); + let mut old_to_current_ids = old_to_existing_ids.clone(); + let mut new_ids = Vec::new(); + + for entry in nodes { + if old_to_existing_ids.contains_key(&entry.id) { + continue; + } + let id = self.add_node(entry.node); + old_to_current_ids.insert(entry.id, id); + new_ids.push(id); + } + + for id in new_ids { + if let Some(mut node) = self.find_node_by_id_mut(id) { + for input in &mut node.inputs { + *input = old_to_current_ids.get(input).copied().unwrap_or(*input); + } + for output in &mut node.outputs { + *output = old_to_current_ids.get(output).copied().unwrap_or(*output); + } + } + } + + old_to_current_ids + } + + pub fn find_node_by_input_name(&mut self, input_name: &str) -> Option> { self.nodes .iter_mut() .find(|node| matches!(&node.kind, GraphNodeKind::Input(name) if name == input_name)) } - pub fn find_node_by_output_name(&mut self, output_name: &str) -> Option<&mut GraphNode> { + pub fn find_node_by_output_name(&mut self, output_name: &str) -> Option> { self.nodes .iter_mut() .find(|node| matches!(&node.kind, GraphNodeKind::Output(name) if name == output_name)) } - pub fn find_node_by_id(&self, node_id: GraphNodeId) -> Option<&GraphNode> { - // TODO: nodes is always sorted by id? - self.nodes - .binary_search_by_key(&node_id, |node| node.id) - .map(|index| &self.nodes[index]) - .ok() + pub fn find_node_by_id(&self, node_id: GraphNodeId) -> Option> { + self.nodes.get(node_id) } - pub fn find_node_by_id_mut(&mut self, node_id: GraphNodeId) -> Option<&mut GraphNode> { - // TODO: nodes is always sorted by id? - self.nodes - .binary_search_by_key(&node_id, |node| node.id) - .map(|index| &mut self.nodes[index]) - .ok() + pub fn find_node_by_id_mut(&mut self, node_id: GraphNodeId) -> Option> { + self.nodes.get_mut(node_id) } pub fn find_and_remove_node_by_id(&mut self, node_id: GraphNodeId) -> Option { - // TODO: nodes is always sorted by id? - self.nodes - .binary_search_by_key(&node_id, |node| node.id) - .map(|index| self.nodes.remove(index)) - .ok() - } - - pub fn rebuild_node_id_base(&mut self, base_index: usize) { - for node in &mut self.nodes { - node.id += base_index; - for index in itertools::chain!(&mut node.inputs, &mut node.outputs) { - *index += base_index; - } - } - - self.build_producers(); - self.build_consumers(); - } - - pub fn rebuild_node_ids_deprecated(self) -> Self { - // toposort가 기존에 없는 새로운 index를 창조함 - // ??? - let index_map = petgraph::algo::toposort(&self.to_petgraph_only_edges(), None) - .unwrap() - .iter() - .enumerate() - .map(|index| (index.1.index(), index.0)) - .collect::>(); - - let mut nodes = self - .nodes - .into_iter() - .map(|node| GraphNode { - id: index_map[&node.id], - inputs: node.inputs.iter().map(|index| index_map[index]).collect(), - outputs: node.outputs.iter().map(|index| index_map[index]).collect(), - kind: node.kind, - tag: node.tag, - ..Default::default() - }) - .collect::>(); - nodes.sort_by_key(|node| node.id); - - let mut result = Self { nodes, ..self }; - result.build_producers(); - result.build_consumers(); - result - } - - pub fn rebuild_node_ids(mut self) -> Self { - self.nodes.sort_by_key(|node| match node.kind { - GraphNodeKind::Input(_) => 0, - GraphNodeKind::Output(_) => 1, - _ => 2, - }); - - let indexs: HashMap<_, _> = self - .nodes - .iter() - .enumerate() - .map(|(index, node)| (node.id, index)) - .collect(); - - let nodes = self - .nodes - .into_iter() - .map(|node| GraphNode { - id: indexs[&node.id], - inputs: node.inputs.iter().map(|index| indexs[index]).collect(), - outputs: node.outputs.iter().map(|index| indexs[index]).collect(), - kind: node.kind, - tag: node.tag, - ..Default::default() - }) - .collect::>(); - - let mut result = Self { nodes, ..self }; - result.build_producers(); - result.build_consumers(); - result + let index = self.nodes.iter().position(|node| node.id == node_id)?; + Some(self.nodes.remove(index)) } pub fn remove_by_node_id_lazy(&mut self, node_id: GraphNodeId) { - let Ok(index) = self.nodes.binary_search_by_key(&node_id, |node| node.id) else { + let Some(index) = self.nodes.iter().position(|node| node.id == node_id) else { return; }; @@ -644,10 +799,10 @@ impl Graph { outputs: Vec, tag: String, ) -> GraphNodeId { - let replacement_id = self.max_node_id().unwrap_or(0) + 1; + let replacement_id = self.nodes.allocate_node_id(); for input in &inputs { - if let Some(node) = self.find_node_by_id_mut(*input) { + if let Some(mut node) = self.find_node_by_id_mut(*input) { node.outputs.retain(|id| !removed_nodes.contains(id)); if !node.outputs.contains(&replacement_id) { node.outputs.push(replacement_id); @@ -655,7 +810,7 @@ impl Graph { } } for output in &outputs { - if let Some(node) = self.find_node_by_id_mut(*output) { + if let Some(mut node) = self.find_node_by_id_mut(*output) { node.inputs.retain(|id| !removed_nodes.contains(id)); if !node.inputs.contains(&replacement_id) { node.inputs.push(replacement_id); @@ -666,14 +821,15 @@ impl Graph { self.remove_by_node_id_lazy(*node_id); } - self.nodes.push(GraphNode { - id: replacement_id, - kind, - inputs, - outputs, - tag, - }); - self.nodes.sort_by_key(|node| node.id); + self.nodes.insert_with_id( + replacement_id, + GraphNode { + kind, + inputs, + outputs, + tag, + }, + ); self.build_inputs(); self.build_outputs(); self.build_producers(); @@ -684,47 +840,58 @@ impl Graph { // 노드 삭제하고 삭제한 노드의 Input Output끼리 연결함 pub fn remove_and_reconnect_by_node_id_lazy(&mut self, node_id: GraphNodeId) { - let Ok(index) = self.nodes.binary_search_by_key(&node_id, |node| node.id) else { + let Some(index) = self.nodes.iter().position(|node| node.id == node_id) else { return; }; let node = self.nodes.remove(index); for input in &node.inputs { - if let Some(input) = self.find_node_by_id_mut(*input) { + if let Some(mut input) = self.find_node_by_id_mut(*input) { input.outputs.extend(&node.outputs); } } for output in &node.outputs { - if let Some(output) = self.find_node_by_id_mut(*output) { + if let Some(mut output) = self.find_node_by_id_mut(*output) { output.inputs.extend(&node.inputs); } } } pub fn replace_node_id_lazy(&mut self, from: GraphNodeId, to: GraphNodeId) { - self.nodes - .iter_mut() - .flat_map(|node| itertools::chain!(&mut node.inputs, &mut node.outputs)) - .filter(|node_id| **node_id == from) - .for_each(|node_id| *node_id = to); + for mut node in self.nodes.iter_mut() { + for node_id in &mut node.inputs { + if *node_id == from { + *node_id = to; + } + } + for node_id in &mut node.outputs { + if *node_id == from { + *node_id = to; + } + } + } } pub fn replace_input_node_id_lazy(&mut self, from: GraphNodeId, to: GraphNodeId) { - self.nodes - .iter_mut() - .flat_map(|node| &mut node.inputs) - .filter(|node_id| **node_id == from) - .for_each(|node_id| *node_id = to); + for mut node in self.nodes.iter_mut() { + for node_id in &mut node.inputs { + if *node_id == from { + *node_id = to; + } + } + } } pub fn replace_output_node_id_lazy(&mut self, from: GraphNodeId, to: GraphNodeId) { - self.nodes - .iter_mut() - .flat_map(|node| &mut node.outputs) - .filter(|node_id| **node_id == from) - .for_each(|node_id| *node_id = to); + for mut node in self.nodes.iter_mut() { + for node_id in &mut node.outputs { + if *node_id == from { + *node_id = to; + } + } + } } pub fn replace_target_input_node_ids( @@ -733,7 +900,7 @@ impl Graph { from: GraphNodeId, to: Vec, ) { - let node = self.find_node_by_id_mut(target).unwrap(); + let mut node = self.find_node_by_id_mut(target).unwrap(); node.inputs.retain(|id| *id != from); node.inputs.extend(to); } @@ -744,7 +911,7 @@ impl Graph { from: GraphNodeId, to: Vec, ) { - let node = self.find_node_by_id_mut(target).unwrap(); + let mut node = self.find_node_by_id_mut(target).unwrap(); node.outputs.retain(|id| *id != from); node.outputs.extend(to); } @@ -762,7 +929,7 @@ impl Graph { }); }); - for node in self.nodes.iter_mut() { + for mut node in self.nodes.iter_mut() { node.inputs = input_map .get(&node.id) .map(|ids| ids.iter().copied().sorted().collect_vec()) @@ -783,7 +950,7 @@ impl Graph { }); }); - for node in self.nodes.iter_mut() { + for mut node in self.nodes.iter_mut() { node.outputs = output_map .get(&node.id) .map(|ids| ids.iter().copied().sorted().collect()) @@ -843,19 +1010,14 @@ impl Graph { .nodes .iter() .filter(|node| node_ids.contains(&node.id)) - .cloned() + .map(|node| (node.id, node.node.clone())) .collect_vec(); - for node in &mut nodes { + for (_, node) in &mut nodes { node.inputs.retain(|input| node_ids.contains(input)); node.outputs.retain(|output| node_ids.contains(output)); } - nodes.sort_by_key(|node| node.id); - - let mut graph = Self { - nodes, - ..Default::default() - }; + let mut graph = Self::from_nodes_with_ids(nodes); graph.build_inputs(); graph.build_outputs(); graph.build_producers(); @@ -871,7 +1033,7 @@ impl Graph { } pub fn critical_path(&self) -> Vec { - fn find_longest_path(graph: &petgraph::Graph<(), ()>) -> Option> { + fn find_longest_path(graph: &petgraph::Graph) -> Option> { let topo_order = petgraph::algo::toposort(&graph, None).unwrap(); let mut max_distances: Vec> = vec![None; graph.node_count()]; @@ -907,10 +1069,11 @@ impl Graph { longest_path.cloned().flatten() } - let mut path = find_longest_path(&self.to_petgraph_only_edges()) + let graph = self.to_petgraph_only_edges(); + let mut path = find_longest_path(&graph) .unwrap() .iter() - .map(|id| id.index()) + .map(|id| graph[*id]) .collect_vec(); path.sort(); path @@ -938,7 +1101,7 @@ impl Graph { |node| matches!(&node.kind, GraphNodeKind::Output(name) if name == output_name), )?; - let id = self.nodes.remove(index).id; + let id = self.nodes.remove_entry(index).id; self.build_outputs(); self.build_producers(); self.build_consumers(); @@ -947,10 +1110,6 @@ impl Graph { } pub fn verify(&self) -> eyre::Result<()> { - if !self.nodes.windows(2).all(|w| w[0].id <= w[1].id) { - eyre::bail!("Nodes must be sorted by id!"); - } - let valid_ids: HashSet = self.nodes.iter().map(|node| node.id).collect(); for node in &self.nodes { @@ -1063,19 +1222,17 @@ impl From<&SubGraphWithGraph<'_>> for Graph { .nodes .iter() .filter(|node| node_ids.contains(&node.id)) - .cloned() + .map(|node| (node.id, node.node.clone())) .collect_vec(); - for node in &mut nodes { + for (_, node) in &mut nodes { node.inputs.retain(|input| node_ids.contains(input)); node.outputs.retain(|output| node_ids.contains(output)); } - let mut graph = Graph { - nodes, - ..Default::default() - }; - graph.rebuild_node_id_base(0); + let mut graph = Graph::from_nodes_with_ids(nodes); + graph.build_producers(); + graph.build_consumers(); graph } @@ -1097,7 +1254,10 @@ impl<'a> From>> for Graph { } } -pub fn subgraphs_to_clustered_graph(graph: &Graph, subgraphs: &[SubGraph]) -> ClusteredGraph { +pub fn subgraphs_to_clustered_graph( + source_graph: &Graph, + subgraphs: &[SubGraph], +) -> ClusteredGraph { // GraphNodeId, Cluster Index let cluster_index: HashMap = subgraphs .iter() @@ -1105,7 +1265,7 @@ pub fn subgraphs_to_clustered_graph(graph: &Graph, subgraphs: &[SubGraph]) -> Cl .flat_map(|(index, sg)| sg.nodes.iter().map(|&id| (id, index)).collect_vec()) .collect(); - let mut nodes = subgraphs + let nodes = subgraphs .iter() .enumerate() .map(|(index, sg)| { @@ -1113,22 +1273,25 @@ pub fn subgraphs_to_clustered_graph(graph: &Graph, subgraphs: &[SubGraph]) -> Cl clustered_type: ClusteredType::Cluster(sg.nodes.clone()), }; - GraphNode { - id: index, - kind: GraphNodeKind::Clustered(clustered), - ..Default::default() - } + ( + index, + GraphNode { + kind: GraphNodeKind::Clustered(clustered), + ..Default::default() + }, + ) }) .collect_vec(); + let mut clustered_graph = Graph::from_nodes_with_ids(nodes); - let mut weighted_node = subgraphs + let weighted_node = subgraphs .iter() .enumerate() .map(|(index, sg)| { let consumers = sg .nodes .iter() - .flat_map(|node| graph.consumers[&node.id()].clone()) + .flat_map(|node| source_graph.consumers[&node.id()].clone()) .collect_vec(); // cluster_index, weight @@ -1159,27 +1322,34 @@ pub fn subgraphs_to_clustered_graph(graph: &Graph, subgraphs: &[SubGraph]) -> Cl }) .collect_vec(); - // Put id lazy - for (index, node) in weighted_node.iter_mut().flatten().enumerate() { - node.id = nodes.len() + index + 1; + let mut weighted_outputs = Vec::new(); + for nodes in weighted_node { + let mut outputs = Vec::new(); + for node in nodes { + let id = clustered_graph.add_node(node); + outputs.push(id); + } + weighted_outputs.push(outputs); } // Set output weighted node - for (index, clustered_node) in nodes.iter_mut().enumerate() { - clustered_node.outputs = weighted_node[index].iter().map(|node| node.id).collect(); + for (index, mut clustered_node) in clustered_graph + .nodes + .iter_mut() + .take(weighted_outputs.len()) + .enumerate() + { + clustered_node.outputs = weighted_outputs[index].clone(); } - let mut graph = Graph { - nodes: [nodes, weighted_node.into_iter().flatten().collect_vec()].concat(), - ..Default::default() - }; - - graph.build_inputs(); - graph.build_producers(); - graph.build_consumers(); - graph.verify().unwrap(); + clustered_graph.build_inputs(); + clustered_graph.build_producers(); + clustered_graph.build_consumers(); + clustered_graph.verify().unwrap(); - ClusteredGraph { graph } + ClusteredGraph { + graph: clustered_graph, + } } #[cfg(test)] @@ -1189,31 +1359,24 @@ mod tests { #[test] fn build_inputs_and_outputs_are_sorted() { - let mut graph = Graph { - nodes: vec![ - GraphNode { - id: 0, - outputs: vec![3], - ..Default::default() - }, - GraphNode { - id: 1, - outputs: vec![3], - ..Default::default() - }, - GraphNode { - id: 2, - outputs: vec![3], - ..Default::default() - }, - GraphNode { - id: 3, - inputs: vec![2, 0, 1], - ..Default::default() - }, - ], - ..Default::default() - }; + let mut graph = Graph::from_nodes(vec![ + GraphNode { + outputs: vec![3], + ..Default::default() + }, + GraphNode { + outputs: vec![3], + ..Default::default() + }, + GraphNode { + outputs: vec![3], + ..Default::default() + }, + GraphNode { + inputs: vec![2, 0, 1], + ..Default::default() + }, + ]); graph.build_inputs(); graph.build_outputs(); @@ -1226,37 +1389,29 @@ mod tests { #[test] fn extract_graph_by_node_ids_keeps_only_internal_edges() { - let mut graph = Graph { - nodes: vec![ - GraphNode { - id: 0, - outputs: vec![2], - ..Default::default() - }, - GraphNode { - id: 1, - outputs: vec![2], - ..Default::default() - }, - GraphNode { - id: 2, - inputs: vec![0, 1], - outputs: vec![3, 4], - ..Default::default() - }, - GraphNode { - id: 3, - inputs: vec![2], - ..Default::default() - }, - GraphNode { - id: 4, - inputs: vec![2], - ..Default::default() - }, - ], - ..Default::default() - }; + let mut graph = Graph::from_nodes(vec![ + GraphNode { + outputs: vec![2], + ..Default::default() + }, + GraphNode { + outputs: vec![2], + ..Default::default() + }, + GraphNode { + inputs: vec![0, 1], + outputs: vec![3, 4], + ..Default::default() + }, + GraphNode { + inputs: vec![2], + ..Default::default() + }, + GraphNode { + inputs: vec![2], + ..Default::default() + }, + ]); graph.build_producers(); graph.build_consumers(); @@ -1288,53 +1443,236 @@ mod tests { #[test] fn sequential_node_kind_is_named_and_verified() -> eyre::Result<()> { - let mut graph = Graph { - nodes: vec![ + let mut graph = Graph::from_nodes(vec![ + GraphNode { + kind: GraphNodeKind::Input("s".to_owned()), + outputs: vec![2], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Input("r".to_owned()), + outputs: vec![2], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Sequential(SequentialPrimitive::new( + SequentialType::RsLatch, + vec!["s".to_owned(), "r".to_owned()], + vec!["q".to_owned()], + )), + inputs: vec![0, 1], + outputs: vec![3], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Output("q".to_owned()), + inputs: vec![2], + ..Default::default() + }, + ]); + + graph.build_inputs(); + graph.build_outputs(); + graph.verify()?; + + let latch = graph.find_node_by_id(2).unwrap(); + assert!(latch.kind.is_sequential()); + assert_eq!(latch.kind.name(), "RsLatch"); + assert_eq!( + latch.kind.as_sequential().unwrap().output_ports, + vec!["q".to_owned()] + ); + + Ok(()) + } + + #[test] + fn sparse_node_ids_are_keyed_and_append_only() { + let mut graph = Graph::from_nodes_with_ids(vec![ + ( + 30, GraphNode { - id: 0, - kind: GraphNodeKind::Input("s".to_owned()), - outputs: vec![2], + inputs: vec![20], ..Default::default() }, + ), + ( + 10, GraphNode { - id: 1, - kind: GraphNodeKind::Input("r".to_owned()), - outputs: vec![2], + outputs: vec![20], ..Default::default() }, + ), + ( + 20, GraphNode { - id: 2, - kind: GraphNodeKind::Sequential(SequentialPrimitive::new( - SequentialType::RsLatch, - vec!["s".to_owned(), "r".to_owned()], - vec!["q".to_owned()], - )), - inputs: vec![0, 1], - outputs: vec![3], + inputs: vec![10], + outputs: vec![30], ..Default::default() }, + ), + ]); + + assert_eq!( + graph.nodes.iter().map(|node| node.id).collect_vec(), + vec![10, 20, 30] + ); + assert_eq!(graph.topological_order(), vec![10, 20, 30]); + assert_eq!( + graph.strongly_connected_components(), + vec![vec![10], vec![20], vec![30]] + ); + assert_eq!(graph.critical_path(), vec![10, 20, 30]); + let petgraph = graph.to_petgraph_only_edges(); + assert_eq!(petgraph.node_count(), 3); + assert_eq!(petgraph.edge_count(), 2); + + graph.remove_by_node_id_lazy(30); + let id = graph.add_node(GraphNode::default()); + + assert_eq!(id, 31); + assert!(graph.find_node_by_id(31).is_some()); + } + + #[test] + fn concat_allocates_fresh_ids_and_remaps_imported_edges() { + let mut graph = Graph::from_nodes_with_ids(vec![( + 10, + GraphNode { + kind: GraphNodeKind::Input("a".to_owned()), + ..Default::default() + }, + )]); + let other = Graph::from_nodes_with_ids(vec![ + ( + 100, GraphNode { - id: 3, - kind: GraphNodeKind::Output("q".to_owned()), - inputs: vec![2], + outputs: vec![101], ..Default::default() }, - ], - ..Default::default() - }; + ), + ( + 101, + GraphNode { + inputs: vec![100], + ..Default::default() + }, + ), + ]); - graph.build_inputs(); - graph.build_outputs(); - graph.verify()?; + graph.concat(other); - let latch = graph.find_node_by_id(2).unwrap(); - assert!(latch.kind.is_sequential()); - assert_eq!(latch.kind.name(), "RsLatch"); assert_eq!( - latch.kind.as_sequential().unwrap().output_ports, - vec!["q".to_owned()] + graph.nodes.iter().map(|node| node.id).collect_vec(), + vec![10, 11, 12] ); + assert_eq!(graph.find_node_by_id(11).unwrap().outputs, vec![12]); + assert_eq!(graph.find_node_by_id(12).unwrap().inputs, vec![11]); + } - Ok(()) + #[test] + fn merge_by_input_allocates_fresh_ids_and_reuses_shared_input() { + let mut graph = Graph::from_nodes_with_ids(vec![( + 10, + GraphNode { + kind: GraphNodeKind::Input("a".to_owned()), + ..Default::default() + }, + )]); + let other = Graph::from_nodes_with_ids(vec![ + ( + 100, + GraphNode { + kind: GraphNodeKind::Input("a".to_owned()), + outputs: vec![101], + ..Default::default() + }, + ), + ( + 101, + GraphNode { + inputs: vec![100], + ..Default::default() + }, + ), + ]); + + graph.merge_by_input(other); + + assert_eq!( + graph.nodes.iter().map(|node| node.id).collect_vec(), + vec![10, 11] + ); + assert_eq!(graph.find_node_by_id(10).unwrap().outputs, vec![11]); + assert_eq!(graph.find_node_by_id(11).unwrap().inputs, vec![10]); + } + + #[test] + fn merge_by_outin_preserves_imported_output_with_same_name_as_removed_output() { + let mut graph = Graph::from_nodes_with_ids(vec![ + ( + 10, + GraphNode { + outputs: vec![11], + ..Default::default() + }, + ), + ( + 11, + GraphNode { + inputs: vec![10], + outputs: vec![12], + ..Default::default() + }, + ), + ( + 12, + GraphNode { + kind: GraphNodeKind::Output("z".to_owned()), + inputs: vec![11], + ..Default::default() + }, + ), + ]); + let target = Graph::from_nodes_with_ids(vec![ + ( + 100, + GraphNode { + kind: GraphNodeKind::Input("i".to_owned()), + outputs: vec![101], + ..Default::default() + }, + ), + ( + 101, + GraphNode { + inputs: vec![100], + outputs: vec![102], + ..Default::default() + }, + ), + ( + 102, + GraphNode { + kind: GraphNodeKind::Output("z".to_owned()), + inputs: vec![101], + ..Default::default() + }, + ), + ]); + + graph.merge_by_outin(target, vec![("z", "i")]); + + let outputs = graph + .nodes + .iter() + .filter(|node| matches!(&node.kind, GraphNodeKind::Output(name) if name == "z")) + .map(|node| node.id) + .collect_vec(); + assert_eq!(outputs, vec![14]); + assert_eq!(graph.find_node_by_id(11).unwrap().outputs, vec![13]); + assert_eq!(graph.find_node_by_id(13).unwrap().inputs, vec![11]); + assert_eq!(graph.find_node_by_id(13).unwrap().outputs, vec![14]); + assert_eq!(graph.find_node_by_id(14).unwrap().inputs, vec![13]); } } diff --git a/src/graph/module.rs b/src/graph/module.rs index 46a2c8a..747d71e 100644 --- a/src/graph/module.rs +++ b/src/graph/module.rs @@ -131,7 +131,8 @@ impl From for GraphModule { .inputs() .iter() .map(|input| { - let input_name = value.find_node_by_id(*input).unwrap().kind.as_input(); + let input_node = value.find_node_by_id(*input).unwrap(); + let input_name = input_node.kind.as_input(); GraphModulePort { port_type: GraphModulePortType::InputNet, target: GraphModulePortTarget::Node(input_name.clone()), @@ -143,7 +144,8 @@ impl From for GraphModule { .outputs() .iter() .map(|output| { - let output_name = value.find_node_by_id(*output).unwrap().kind.as_output(); + let output_node = value.find_node_by_id(*output).unwrap(); + let output_name = output_node.kind.as_output(); GraphModulePort { port_type: GraphModulePortType::OutputNet, target: GraphModulePortTarget::Node(output_name.clone()), @@ -244,7 +246,7 @@ impl From<(&GraphModuleContext, &GraphModule)> for GraphWithSubGraphs { graph .nodes .iter_mut() - .for_each(|node| match &mut node.kind { + .for_each(|mut node| match &mut node.kind { GraphNodeKind::Input(name) | GraphNodeKind::Output(name) => { if let Some(port_name) = module_port_name .get_mut(instance) diff --git a/src/graph/world.rs b/src/graph/world.rs index d34ae62..039be71 100644 --- a/src/graph/world.rs +++ b/src/graph/world.rs @@ -97,10 +97,7 @@ impl WorldGraphBuilder { self.visit_blocks(); let (nodes, positions) = self.build_nodes(); - let mut graph = Graph { - nodes, - ..Default::default() - }; + let mut graph = Graph::from_nodes_with_ids(nodes); graph.build_inputs(); graph.build_producers(); @@ -146,7 +143,12 @@ impl WorldGraphBuilder { } } - fn build_nodes(&mut self) -> (Vec, HashMap) { + fn build_nodes( + &mut self, + ) -> ( + Vec<(GraphNodeId, GraphNode)>, + HashMap, + ) { let mut graph_id: HashMap = HashMap::new(); let mut nodes: HashMap = HashMap::new(); @@ -157,10 +159,7 @@ impl WorldGraphBuilder { .enumerate() { graph_id.insert(*pos, index); - nodes.insert( - *pos, - GraphNode::new(index, GraphNodeKind::Block(self.world[*pos])), - ); + nodes.insert(*pos, GraphNode::new(GraphNodeKind::Block(self.world[*pos]))); } for pos in self @@ -197,10 +196,16 @@ impl WorldGraphBuilder { } } - let positions = nodes.iter().map(|(pos, node)| (node.id, *pos)).collect(); - let mut nodes = nodes.into_values().collect_vec(); + let positions = nodes + .keys() + .map(|pos| (graph_id[pos], *pos)) + .collect::>(); + let mut nodes = nodes + .into_iter() + .map(|(pos, node)| (graph_id[&pos], node)) + .collect_vec(); - nodes.sort_by(|a, b| a.id.cmp(&b.id)); + nodes.sort_by_key(|(id, _)| *id); (nodes, positions) } diff --git a/src/sequential/core.rs b/src/sequential/core.rs index 10a2480..ba497ae 100644 --- a/src/sequential/core.rs +++ b/src/sequential/core.rs @@ -101,34 +101,28 @@ pub fn rs_latch_prefix_graph(graph: &Graph, core: &RsLatchCore) -> Option>(); - let mut next_node_id = graph.nodes.iter().map(|node| node.id).max().unwrap_or(0) + 1; + let mut graph = Graph::from_nodes_with_ids(nodes); for (output_name, source_node_id) in output_roots { - nodes.push(GraphNode { - id: next_node_id, + graph.add_node(GraphNode { kind: GraphNodeKind::Output(output_name.to_owned()), inputs: vec![source_node_id], ..Default::default() }); - next_node_id += 1; } - nodes.sort_by_key(|node| node.id); - - let mut graph = Graph { - nodes, - ..Default::default() - }; graph.build_outputs(); graph.build_producers(); graph.build_consumers(); diff --git a/src/sequential/mod.rs b/src/sequential/mod.rs index d48c623..6305932 100644 --- a/src/sequential/mod.rs +++ b/src/sequential/mod.rs @@ -103,65 +103,54 @@ fn default_inner_graph(sequential_type: SequentialType) -> Graph { fn rs_latch_inner_graph() -> Graph { // q = ~(r | nq), nq = ~(s | q) - let mut graph = Graph { - nodes: vec![ - GraphNode { - id: 0, - kind: GraphNodeKind::Input("s".to_owned()), - ..Default::default() - }, - GraphNode { - id: 1, - kind: GraphNodeKind::Input("r".to_owned()), - ..Default::default() - }, - GraphNode { - id: 2, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::Or, - }), - inputs: vec![1, 5], - ..Default::default() - }, - GraphNode { - id: 3, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::Not, - }), - inputs: vec![2], - ..Default::default() - }, - GraphNode { - id: 4, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::Or, - }), - inputs: vec![0, 3], - ..Default::default() - }, - GraphNode { - id: 5, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::Not, - }), - inputs: vec![4], - ..Default::default() - }, - GraphNode { - id: 6, - kind: GraphNodeKind::Output("q".to_owned()), - inputs: vec![3], - ..Default::default() - }, - GraphNode { - id: 7, - kind: GraphNodeKind::Output("nq".to_owned()), - inputs: vec![5], - ..Default::default() - }, - ], - ..Default::default() - }; + let mut graph = Graph::from_nodes(vec![ + GraphNode { + kind: GraphNodeKind::Input("s".to_owned()), + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Input("r".to_owned()), + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::Or, + }), + inputs: vec![1, 5], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::Not, + }), + inputs: vec![2], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::Or, + }), + inputs: vec![0, 3], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::Not, + }), + inputs: vec![4], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Output("q".to_owned()), + inputs: vec![3], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Output("nq".to_owned()), + inputs: vec![5], + ..Default::default() + }, + ]); graph.build_outputs(); graph.build_producers(); graph.build_consumers(); @@ -171,89 +160,75 @@ fn rs_latch_inner_graph() -> Graph { fn d_latch_inner_graph() -> Graph { // s = d & en, r = ~d & en, q = ~(r | nq), nq = ~(s | q) - let mut graph = Graph { - nodes: vec![ - GraphNode { - id: 0, - kind: GraphNodeKind::Input("d".to_owned()), - ..Default::default() - }, - GraphNode { - id: 1, - kind: GraphNodeKind::Input("en".to_owned()), - ..Default::default() - }, - GraphNode { - id: 2, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::And, - }), - inputs: vec![0, 1], - ..Default::default() - }, - GraphNode { - id: 3, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::Not, - }), - inputs: vec![0], - ..Default::default() - }, - GraphNode { - id: 4, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::And, - }), - inputs: vec![3, 1], - ..Default::default() - }, - GraphNode { - id: 5, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::Or, - }), - inputs: vec![4, 8], - ..Default::default() - }, - GraphNode { - id: 6, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::Not, - }), - inputs: vec![5], - ..Default::default() - }, - GraphNode { - id: 7, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::Or, - }), - inputs: vec![2, 6], - ..Default::default() - }, - GraphNode { - id: 8, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::Not, - }), - inputs: vec![7], - ..Default::default() - }, - GraphNode { - id: 9, - kind: GraphNodeKind::Output("q".to_owned()), - inputs: vec![6], - ..Default::default() - }, - GraphNode { - id: 10, - kind: GraphNodeKind::Output("nq".to_owned()), - inputs: vec![8], - ..Default::default() - }, - ], - ..Default::default() - }; + let mut graph = Graph::from_nodes(vec![ + GraphNode { + kind: GraphNodeKind::Input("d".to_owned()), + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Input("en".to_owned()), + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::And, + }), + inputs: vec![0, 1], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::Not, + }), + inputs: vec![0], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::And, + }), + inputs: vec![3, 1], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::Or, + }), + inputs: vec![4, 8], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::Not, + }), + inputs: vec![5], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::Or, + }), + inputs: vec![2, 6], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::Not, + }), + inputs: vec![7], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Output("q".to_owned()), + inputs: vec![6], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Output("nq".to_owned()), + inputs: vec![8], + ..Default::default() + }, + ]); graph.build_outputs(); graph.build_producers(); graph.build_consumers(); diff --git a/src/transform/logic.rs b/src/transform/logic.rs index afd72ef..fa89f74 100644 --- a/src/transform/logic.rs +++ b/src/transform/logic.rs @@ -209,8 +209,6 @@ impl LogicGraphTransformer { // replacable only (x, y) => (z) fn replace_binops_lazy(&mut self, src: GraphNodeId, mut tar: Graph) -> eyre::Result<()> { - tar.rebuild_node_id_base(self.graph.graph.max_node_id().unwrap() + 1); - let g = &mut self.graph.graph; let src_inputs = g.find_node_by_id(src).unwrap().inputs.clone(); @@ -224,26 +222,33 @@ impl LogicGraphTransformer { .collect_vec(); let tar_output = tar.outputs()[0]; let tar_output_input = tar.find_node_by_id(tar.outputs()[0]).unwrap().inputs[0]; + let old_to_existing_ids = HashMap::from([ + (tar_inputs[0], src_inputs[0]), + (tar_inputs[1], src_inputs[1]), + ]); + + tar.remove_by_node_id_lazy(tar_output); + let old_to_current_ids = g.append_graph_with_replacements(tar, &old_to_existing_ids); for index in 0..=1 { - if let Some(node) = g.find_node_by_id_mut(src_inputs[index]) { - node.outputs.extend(tar_inputs_outputs[index].clone()); + if let Some(mut node) = g.find_node_by_id_mut(src_inputs[index]) { + node.outputs.extend( + tar_inputs_outputs[index] + .iter() + .map(|id| old_to_current_ids.get(id).copied().unwrap_or(*id)), + ); node.outputs.retain(|node_id| *node_id != src); } } - tar.replace_node_id_lazy(tar_inputs[0], src_inputs[0]); - tar.replace_node_id_lazy(tar_inputs[1], src_inputs[1]); + let tar_output_input = old_to_current_ids + .get(&tar_output_input) + .copied() + .unwrap_or(tar_output_input); g.replace_input_node_id_lazy(src, tar_output_input); g.remove_by_node_id_lazy(src); - - tar.find_node_by_id_mut(tar_output_input).unwrap().outputs = src_outputs.clone(); - tar.remove_by_node_id_lazy(tar_inputs[0]); - tar.remove_by_node_id_lazy(tar_inputs[1]); - tar.remove_by_node_id_lazy(tar_output); - - g.nodes.extend(tar.nodes); + g.find_node_by_id_mut(tar_output_input).unwrap().outputs = src_outputs; Ok(()) } @@ -254,28 +259,45 @@ impl LogicGraphTransformer { } // optimize logic graph using quine mccluskey + let input_nodes = self + .graph + .graph + .nodes + .iter() + .filter(|node| matches!(node.kind, GraphNodeKind::Input(_))) + .map(|node| (node.id, node.clone_node())) + .collect_vec(); + let input_terms = input_nodes + .iter() + .enumerate() + .map(|(index, (node_id, _))| (*node_id, index as u8)) + .collect::>(); + fn make_qmc_form( graph: &Graph, + input_terms: &HashMap, node_id: GraphNodeId, ) -> eyre::Result { let node = graph.find_node_by_id(node_id).unwrap(); match &node.kind { - GraphNodeKind::Input(_) => Ok(quine_mc_cluskey::Bool::Term(node_id as u8)), - GraphNodeKind::Output(_) => make_qmc_form(graph, node.inputs[0]), + GraphNodeKind::Input(_) => Ok(quine_mc_cluskey::Bool::Term(input_terms[&node_id])), + GraphNodeKind::Output(_) => make_qmc_form(graph, input_terms, node.inputs[0]), GraphNodeKind::Logic(logic) => Ok(match &logic.logic_type { - LogicType::Not => { - quine_mc_cluskey::Bool::Not(Box::new(make_qmc_form(graph, node.inputs[0])?)) - } + LogicType::Not => quine_mc_cluskey::Bool::Not(Box::new(make_qmc_form( + graph, + input_terms, + node.inputs[0], + )?)), LogicType::And => quine_mc_cluskey::Bool::And( node.inputs .iter() - .map(|input| make_qmc_form(graph, *input).unwrap()) + .map(|input| make_qmc_form(graph, input_terms, *input).unwrap()) .collect(), ), LogicType::Or => quine_mc_cluskey::Bool::Or( node.inputs .iter() - .map(|input| make_qmc_form(graph, *input).unwrap()) + .map(|input| make_qmc_form(graph, input_terms, *input).unwrap()) .collect(), ), LogicType::Xor => unimplemented!(), @@ -284,76 +306,67 @@ impl LogicGraphTransformer { } } - let results = make_qmc_form(&self.graph.graph, self.graph.graph.outputs()[0])?.simplify(); - - let mut nodes = Vec::new(); + let results = make_qmc_form( + &self.graph.graph, + &input_terms, + self.graph.graph.outputs()[0], + )? + .simplify(); fn make_rc_form( - nodes: &mut Vec, - id: &mut usize, - lookup: &Vec, + graph: &mut Graph, + lookup: &Vec<(GraphNodeId, GraphNode)>, node: &quine_mc_cluskey::Bool, ) -> GraphNodeId { - let tid = *id; - *id += 1; - let node = match node { quine_mc_cluskey::Bool::Term(v) => GraphNode { - id: tid, - kind: GraphNodeKind::Input(lookup[*v as usize].kind.as_input().to_owned()), + kind: GraphNodeKind::Input(lookup[*v as usize].1.kind.as_input().to_owned()), ..Default::default() }, quine_mc_cluskey::Bool::And(op) => GraphNode { - id: tid, kind: GraphNodeKind::Logic(Logic { logic_type: LogicType::And, }), inputs: op .iter() - .map(|v| make_rc_form(nodes, id, lookup, v)) + .map(|v| make_rc_form(graph, lookup, v)) .collect_vec(), ..Default::default() }, quine_mc_cluskey::Bool::Or(op) => GraphNode { - id: tid, kind: GraphNodeKind::Logic(Logic { logic_type: LogicType::Or, }), inputs: op .iter() - .map(|v| make_rc_form(nodes, id, lookup, v)) + .map(|v| make_rc_form(graph, lookup, v)) .collect_vec(), ..Default::default() }, quine_mc_cluskey::Bool::Not(v) => GraphNode { - id: tid, kind: GraphNodeKind::Logic(Logic { logic_type: LogicType::Not, }), - inputs: vec![make_rc_form(nodes, id, lookup, v)], + inputs: vec![make_rc_form(graph, lookup, v)], ..Default::default() }, _ => unreachable!(), }; if let GraphNodeKind::Input(name) = &node.kind { - if let Some(node) = nodes - .iter() - .find(|node| matches!(&node.kind, GraphNodeKind::Input(other) if name == other)) - { - *id -= 1; - return node.id; + if let Some(existing) = graph.nodes.iter().find( + |existing| matches!(&existing.kind, GraphNodeKind::Input(other) if name == other), + ) { + return existing.id; } } - nodes.push(node); - tid + graph.add_node(node) } - let mut id = 0; - let node = make_rc_form(&mut nodes, &mut id, &self.graph.graph.nodes, &results[0]); - let output_node = GraphNode { - id: nodes.len(), + let mut graph = Graph::default(); + let node = make_rc_form(&mut graph, &input_nodes, &results[0]); + graph.add_node(GraphNode { kind: GraphNodeKind::Output( self.graph .graph @@ -365,11 +378,8 @@ impl LogicGraphTransformer { ), inputs: vec![node], ..Default::default() - }; - nodes.push(output_node); - nodes.sort_by_key(|node| node.id); - - self.graph.graph.nodes = nodes; + }); + self.graph.graph = graph; self.graph.graph.build_outputs(); self.graph.graph.build_producers(); self.graph.graph.build_consumers(); @@ -449,47 +459,33 @@ impl LogicGraphTransformer { } pub fn insert_buffers_for_direct_or_to_or(&mut self) -> eyre::Result<()> { - let direct_edges = self - .graph - .graph - .nodes - .iter() - .filter(|node| { - matches!(&node.kind, GraphNodeKind::Logic(logic) if logic.logic_type == LogicType::Or) - }) - .flat_map(|node| { - node.outputs.iter().filter_map(|&output_id| { - let output = self.graph.graph.find_node_by_id(output_id)?; - matches!(&output.kind, GraphNodeKind::Logic(logic) if logic.logic_type == LogicType::Or) - .then_some((node.id, output.id)) - }) - }) - .collect_vec(); + let mut direct_edges = Vec::new(); + for node in self.graph.graph.nodes.iter() { + if !matches!(&node.kind, GraphNodeKind::Logic(logic) if logic.logic_type == LogicType::Or) + { + continue; + } + for &output_id in &node.outputs { + let Some(output) = self.graph.graph.find_node_by_id(output_id) else { + continue; + }; + if matches!(&output.kind, GraphNodeKind::Logic(logic) if logic.logic_type == LogicType::Or) + { + direct_edges.push((node.id, output.id)); + } + } + } - let mut next_id = self.graph.graph.max_node_id().unwrap_or_default() + 1; for (from, to) in direct_edges { - let first_not = next_id; - let second_not = next_id + 1; - next_id += 2; - - self.graph - .graph - .replace_target_output_node_ids(from, to, vec![first_not]); - self.graph - .graph - .replace_target_input_node_ids(to, from, vec![second_not]); - - self.graph.graph.nodes.push(GraphNode { - id: first_not, + let first_not = self.graph.graph.add_node(GraphNode { kind: GraphNodeKind::Logic(Logic { logic_type: LogicType::Not, }), inputs: vec![from], - outputs: vec![second_not], tag: "auto-buffer".to_owned(), + ..Default::default() }); - self.graph.graph.nodes.push(GraphNode { - id: second_not, + let second_not = self.graph.graph.add_node(GraphNode { kind: GraphNodeKind::Logic(Logic { logic_type: LogicType::Not, }), @@ -497,6 +493,18 @@ impl LogicGraphTransformer { outputs: vec![to], tag: "auto-buffer".to_owned(), }); + self.graph + .graph + .find_node_by_id_mut(first_not) + .unwrap() + .outputs = vec![second_not]; + + self.graph + .graph + .replace_target_output_node_ids(from, to, vec![first_not]); + self.graph + .graph + .replace_target_input_node_ids(to, from, vec![second_not]); } self.graph.graph.build_inputs(); @@ -662,49 +670,41 @@ mod tests { #[test] fn prepare_place_preserves_sequential_boundaries() -> eyre::Result<()> { - let mut graph = Graph { - nodes: vec![ - GraphNode { - id: 0, - kind: GraphNodeKind::Input("s".to_owned()), - outputs: vec![2], - ..Default::default() - }, - GraphNode { - id: 1, - kind: GraphNodeKind::Input("r".to_owned()), - outputs: vec![2], - ..Default::default() - }, - GraphNode { - id: 2, - kind: GraphNodeKind::Sequential(SequentialPrimitive::new( - SequentialType::RsLatch, - vec!["s".to_owned(), "r".to_owned()], - vec!["q".to_owned()], - )), - inputs: vec![0, 1], - outputs: vec![3], - ..Default::default() - }, - GraphNode { - id: 3, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::Not, - }), - inputs: vec![2], - outputs: vec![4], - ..Default::default() - }, - GraphNode { - id: 4, - kind: GraphNodeKind::Output("not_q".to_owned()), - inputs: vec![3], - ..Default::default() - }, - ], - ..Default::default() - }; + let mut graph = Graph::from_nodes(vec![ + GraphNode { + kind: GraphNodeKind::Input("s".to_owned()), + outputs: vec![2], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Input("r".to_owned()), + outputs: vec![2], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Sequential(SequentialPrimitive::new( + SequentialType::RsLatch, + vec!["s".to_owned(), "r".to_owned()], + vec!["q".to_owned()], + )), + inputs: vec![0, 1], + outputs: vec![3], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::Not, + }), + inputs: vec![2], + outputs: vec![4], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Output("not_q".to_owned()), + inputs: vec![3], + ..Default::default() + }, + ]); graph.build_inputs(); graph.build_outputs(); diff --git a/src/transform/place_and_route/local_placer/component_tests.rs b/src/transform/place_and_route/local_placer/component_tests.rs index a7ea6d0..10eb318 100644 --- a/src/transform/place_and_route/local_placer/component_tests.rs +++ b/src/transform/place_and_route/local_placer/component_tests.rs @@ -1,7 +1,7 @@ use crate::graph::analysis::equivalent_expression_groups; use crate::graph::graphviz::ToGraphvizGraph; use crate::graph::logic::predefined_logics; -use crate::graph::{GraphNode, GraphNodeKind}; +use crate::graph::{GraphNode, GraphNodeKind, GraphNodeRef}; use crate::nbt::{NBTRoot, ToNBT}; use crate::sequential::{SequentialPrimitive, SequentialType}; use crate::transform::place_and_route::estimate::world_compact_cost; @@ -304,24 +304,25 @@ fn test_generate_component_rs_latch() -> eyre::Result<()> { vec!["s".to_owned(), "r".to_owned()], vec!["q".to_owned()], ); + let node_id = 2; let node = GraphNode { - id: 2, kind: GraphNodeKind::Sequential(sequential.clone()), inputs: vec![0, 1], ..Default::default() }; + let node_ref = GraphNodeRef::new(node_id, &node); let state = [(0, s), (1, r)].into_iter().collect(); let pairs = select_rs_latch_not_pairs( &config, - &node, + node_ref, &sequential, &state, generate_rs_latch_not_pairs(&world), ); let generated = pairs .into_iter() - .flat_map(|placed| route_rs_latch_branches(&config, &node, &sequential, &state, placed)) + .flat_map(|placed| route_rs_latch_branches(&config, node_ref, &sequential, &state, placed)) .collect::>(); assert!(!generated.is_empty()); let valid = generated.into_iter().find_map(|placed| { @@ -372,15 +373,16 @@ fn test_generate_component_d_latch() -> eyre::Result<()> { vec!["d".to_owned(), "en".to_owned()], vec!["q".to_owned()], ); + let node_id = 2; let node = GraphNode { - id: 2, kind: GraphNodeKind::Sequential(sequential.clone()), inputs: vec![0, 1], ..Default::default() }; + let node_ref = GraphNodeRef::new(node_id, &node); let state = [(0, d), (1, en)].into_iter().collect(); - let generated = generate_d_latch_gate_routes(&config, &node, &sequential, &world, &state); + let generated = generate_d_latch_gate_routes(&config, node_ref, &sequential, &world, &state); assert!(!generated.is_empty()); let mut checked = 0usize; let valid = generated.iter().find_map(|(world, state)| { @@ -394,7 +396,7 @@ fn test_generate_component_d_latch() -> eyre::Result<()> { eprintln!("candidate {checked} failed: {error}; q={q:?} nq={nq:?}"); let q_support = q.walk(world[q].direction).unwrap(); let nq_support = nq.walk(world[nq].direction).unwrap(); - let nodes = rs_latch_input_node_ids(node.id); + let nodes = rs_latch_input_node_ids(node_id); let _ = trace_d_latch_behavior( world, d, diff --git a/src/transform/place_and_route/local_placer/mod.rs b/src/transform/place_and_route/local_placer/mod.rs index 2fde7c2..562c7d2 100644 --- a/src/transform/place_and_route/local_placer/mod.rs +++ b/src/transform/place_and_route/local_placer/mod.rs @@ -10,7 +10,7 @@ use super::place_bound::PlaceBound; use super::placed_node::PlacedNode; use super::sampling::SamplingPolicy; use crate::graph::logic::LogicGraph; -use crate::graph::{GraphNode, GraphNodeId, GraphNodeKind}; +use crate::graph::{GraphNodeId, GraphNodeKind, GraphNodeRef}; use crate::logic::LogicType; use crate::output::{OutputEndpoint, PlacedWorld}; use crate::sequential::layout::SequentialMacro; @@ -291,7 +291,7 @@ impl LocalPlacer { fn generate_place_and_route( &self, - node: &GraphNode, + node: GraphNodeRef<'_>, world: World3D, state: &PlacementState, ) -> PlacementGeneration { @@ -482,7 +482,7 @@ impl LocalPlacer { .filter(|node| { self.config.materialize_outputs || !matches!(node.kind, GraphNodeKind::Output(_)) }) - .flat_map(|node| node.inputs.iter().copied()) + .flat_map(|node| node.inputs.clone()) .chain(self.graph.externally_observable_output_source_ids()) .chain( self.config diff --git a/src/transform/place_and_route/local_placer/sequential/d_latch.rs b/src/transform/place_and_route/local_placer/sequential/d_latch.rs index 43e23e6..df8ff30 100644 --- a/src/transform/place_and_route/local_placer/sequential/d_latch.rs +++ b/src/transform/place_and_route/local_placer/sequential/d_latch.rs @@ -4,6 +4,7 @@ use super::prefix::RsLatchPrefixPlan; use super::rs_latch::generate_rs_latch_gate_routes; use super::scenario::{run_sequential_scenario, torch_output_value, SequentialScenarioStep}; use super::*; +use crate::graph::GraphNode; use crate::world::simulator::Simulator; use crate::world::World; @@ -19,7 +20,7 @@ const D_LATCH_SIM_TRACE_LIMIT: usize = 0; pub(in super::super) fn generate_d_latch_gate_routes( config: &LocalPlacerConfig, - node: &GraphNode, + node: GraphNodeRef<'_>, sequential: &SequentialPrimitive, world: &World3D, state: &PlacementState, @@ -37,11 +38,11 @@ pub(in super::super) fn generate_d_latch_gate_routes( sequential.output_ports.clone(), ); let rs_node = GraphNode { - id: node.id, kind: GraphNodeKind::Sequential(rs_sequential.clone()), inputs: prefix_plan.rs_node_inputs(), ..Default::default() }; + let rs_node = GraphNodeRef::new(node.id, &rs_node); let queue = prefix_plan.place(config, world, state); let queue = retain_valid_d_latch_prefix(config, queue, &prefix_plan); @@ -52,7 +53,7 @@ pub(in super::super) fn generate_d_latch_gate_routes( &prefix_plan, data_input, enable_input, - &rs_node, + rs_node, &rs_sequential, queue, ); @@ -65,12 +66,12 @@ pub(in super::super) fn generate_d_latch_gate_routes( fn route_d_latch_core( config: &LocalPlacerConfig, - node: &GraphNode, + node: GraphNodeRef<'_>, outer_state: &PlacementState, prefix_plan: &RsLatchPrefixPlan, data_input: GraphNodeId, enable_input: GraphNodeId, - rs_node: &GraphNode, + rs_node: GraphNodeRef<'_>, rs_sequential: &SequentialPrimitive, queue: PlacerQueue, ) -> PlacerQueue { diff --git a/src/transform/place_and_route/local_placer/sequential/macro_routes.rs b/src/transform/place_and_route/local_placer/sequential/macro_routes.rs index b094ef0..a33e3a3 100644 --- a/src/transform/place_and_route/local_placer/sequential/macro_routes.rs +++ b/src/transform/place_and_route/local_placer/sequential/macro_routes.rs @@ -6,7 +6,7 @@ use super::*; pub(in super::super) fn generate_sequential_macro_routes( config: &LocalPlacerConfig, - node: &GraphNode, + node: GraphNodeRef<'_>, sequential: &SequentialPrimitive, world: &World3D, state: &PlacementState, @@ -73,7 +73,7 @@ pub(in super::super) fn place_sequential_macro( pub(in super::super) fn route_sequential_inputs( config: &LocalPlacerConfig, - node: &GraphNode, + node: GraphNodeRef<'_>, sequential: &SequentialPrimitive, state: &PlacementState, placed: &PlacedSequentialMacro, diff --git a/src/transform/place_and_route/local_placer/sequential/prefix.rs b/src/transform/place_and_route/local_placer/sequential/prefix.rs index 19a3a30..a72124a 100644 --- a/src/transform/place_and_route/local_placer/sequential/prefix.rs +++ b/src/transform/place_and_route/local_placer/sequential/prefix.rs @@ -15,7 +15,7 @@ pub(super) struct RsLatchPrefixPlan { impl RsLatchPrefixPlan { pub(super) fn build( - node: &GraphNode, + node: GraphNodeRef<'_>, sequential: &SequentialPrimitive, state: &PlacementState, ) -> Option { @@ -29,14 +29,14 @@ impl RsLatchPrefixPlan { let mut input_node_ids_by_port = HashMap::new(); let mut input_positions_by_port = HashMap::new(); for prefix_input in graph.nodes.iter().filter_map(|node| match &node.kind { - GraphNodeKind::Input(name) => Some((node.id, name.as_str())), + GraphNodeKind::Input(name) => Some((node.id, name.clone())), _ => None, }) { - let outer_input = outer_input_node_id(node, sequential, prefix_input.1)?; + let outer_input = outer_input_node_id(node, sequential, &prefix_input.1)?; let position = state.node_position(outer_input)?; input_mappings.push((prefix_input.0, outer_input)); - input_node_ids_by_port.insert(prefix_input.1.to_owned(), outer_input); - input_positions_by_port.insert(prefix_input.1.to_owned(), position); + input_node_ids_by_port.insert(prefix_input.1.clone(), outer_input); + input_positions_by_port.insert(prefix_input.1, position); } Some(Self { @@ -115,7 +115,7 @@ impl RsLatchPrefixPlan { } fn outer_input_node_id( - node: &GraphNode, + node: GraphNodeRef<'_>, sequential: &SequentialPrimitive, port: &str, ) -> Option { diff --git a/src/transform/place_and_route/local_placer/sequential/rs_latch.rs b/src/transform/place_and_route/local_placer/sequential/rs_latch.rs index c75db1e..9a70af5 100644 --- a/src/transform/place_and_route/local_placer/sequential/rs_latch.rs +++ b/src/transform/place_and_route/local_placer/sequential/rs_latch.rs @@ -28,7 +28,7 @@ pub(in super::super) struct RsLatchGatePlacement { pub(in super::super) fn generate_rs_latch_gate_routes( config: &LocalPlacerConfig, - node: &GraphNode, + node: GraphNodeRef<'_>, sequential: &SequentialPrimitive, world: &World3D, state: &PlacementState, @@ -72,7 +72,7 @@ pub(in super::super) fn generate_rs_latch_gate_routes( pub(in super::super) fn select_rs_latch_not_pairs( config: &LocalPlacerConfig, - node: &GraphNode, + node: GraphNodeRef<'_>, sequential: &SequentialPrimitive, state: &PlacementState, mut pairs: Vec, @@ -130,7 +130,7 @@ fn lies_between(start: Position, end: Position, position: Position) -> bool { pub(in super::super) fn route_rs_latch_branches( config: &LocalPlacerConfig, - node: &GraphNode, + node: GraphNodeRef<'_>, sequential: &SequentialPrimitive, state: &PlacementState, placed: RsLatchGatePlacement, @@ -259,7 +259,7 @@ fn redstone_route_network( } fn rs_latch_input_source( - node: &GraphNode, + node: GraphNodeRef<'_>, sequential: &SequentialPrimitive, state: &PlacementState, port: &str, diff --git a/src/transform/place_and_route/local_placer/tests.rs b/src/transform/place_and_route/local_placer/tests.rs index f9a69a0..b6b2ff8 100644 --- a/src/transform/place_and_route/local_placer/tests.rs +++ b/src/transform/place_and_route/local_placer/tests.rs @@ -1,6 +1,6 @@ use super::*; use crate::graph::logic::{predefined_logics, LogicGraph}; -use crate::graph::{Graph, GraphNode, GraphNodeKind}; +use crate::graph::{Graph, GraphNode, GraphNodeKind, GraphNodeRef}; use crate::logic::LogicType; use crate::sequential::layout::SequentialMacro; use crate::sequential::{SequentialPrimitive, SequentialType}; @@ -343,60 +343,49 @@ fn generate_with_outputs_reports_materialized_output_positions() -> eyre::Result #[test] fn future_join_cost_weights_pairs_with_remaining_fanout() { - let mut graph = Graph { - nodes: vec![ - GraphNode { - id: 0, - kind: GraphNodeKind::Input("a".to_owned()), - ..Default::default() - }, - GraphNode { - id: 1, - kind: GraphNodeKind::Input("b".to_owned()), - ..Default::default() - }, - GraphNode { - id: 2, - kind: GraphNodeKind::Input("c".to_owned()), - ..Default::default() - }, - GraphNode { - id: 3, - kind: GraphNodeKind::Input("d".to_owned()), - ..Default::default() - }, - GraphNode { - id: 4, - kind: GraphNodeKind::Input("e".to_owned()), - ..Default::default() - }, - GraphNode { - id: 5, - kind: GraphNodeKind::Logic(crate::logic::Logic { - logic_type: LogicType::Or, - }), - inputs: vec![0, 1], - ..Default::default() - }, - GraphNode { - id: 6, - kind: GraphNodeKind::Logic(crate::logic::Logic { - logic_type: LogicType::Or, - }), - inputs: vec![0, 2], - ..Default::default() - }, - GraphNode { - id: 7, - kind: GraphNodeKind::Logic(crate::logic::Logic { - logic_type: LogicType::Or, - }), - inputs: vec![3, 4], - ..Default::default() - }, - ], - ..Default::default() - }; + let mut graph = Graph::from_nodes(vec![ + GraphNode { + kind: GraphNodeKind::Input("a".to_owned()), + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Input("b".to_owned()), + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Input("c".to_owned()), + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Input("d".to_owned()), + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Input("e".to_owned()), + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(crate::logic::Logic { + logic_type: LogicType::Or, + }), + inputs: vec![0, 1], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(crate::logic::Logic { + logic_type: LogicType::Or, + }), + inputs: vec![0, 2], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Logic(crate::logic::Logic { + logic_type: LogicType::Or, + }), + inputs: vec![3, 4], + ..Default::default() + }, + ]); graph.build_outputs(); let graph = LogicGraph { graph }; let visit_orders = vec![0, 1, 2, 3, 4, 5, 6, 7]; @@ -792,40 +781,33 @@ fn generate_or_routes_finds_adjacent_torch_route() { #[test] fn local_placer_accepts_q_only_sequential_primitives_with_macro_candidates() { - let mut graph = Graph { - nodes: vec![ - GraphNode { - id: 0, - kind: GraphNodeKind::Input("s".to_owned()), - outputs: vec![2], - ..Default::default() - }, - GraphNode { - id: 1, - kind: GraphNodeKind::Input("r".to_owned()), - outputs: vec![2], - ..Default::default() - }, - GraphNode { - id: 2, - kind: GraphNodeKind::Sequential(SequentialPrimitive::new( - SequentialType::RsLatch, - vec!["s".to_owned(), "r".to_owned()], - vec!["q".to_owned()], - )), - inputs: vec![0, 1], - outputs: vec![3], - ..Default::default() - }, - GraphNode { - id: 3, - kind: GraphNodeKind::Output("q".to_owned()), - inputs: vec![2], - ..Default::default() - }, - ], - ..Default::default() - }; + let mut graph = Graph::from_nodes(vec![ + GraphNode { + kind: GraphNodeKind::Input("s".to_owned()), + outputs: vec![2], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Input("r".to_owned()), + outputs: vec![2], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Sequential(SequentialPrimitive::new( + SequentialType::RsLatch, + vec!["s".to_owned(), "r".to_owned()], + vec!["q".to_owned()], + )), + inputs: vec![0, 1], + outputs: vec![3], + ..Default::default() + }, + GraphNode { + kind: GraphNodeKind::Output("q".to_owned()), + inputs: vec![2], + ..Default::default() + }, + ]); graph.build_inputs(); graph.build_outputs(); @@ -836,15 +818,11 @@ fn local_placer_accepts_q_only_sequential_primitives_with_macro_candidates() { #[test] fn local_placer_rejects_multi_output_sequential_primitives_until_edges_are_port_aware() { - let mut graph = Graph { - nodes: vec![GraphNode { - id: 0, - kind: GraphNodeKind::Sequential(SequentialPrimitive::rs_latch()), - outputs: vec![1], - ..Default::default() - }], + let mut graph = Graph::from_nodes(vec![GraphNode { + kind: GraphNodeKind::Sequential(SequentialPrimitive::rs_latch()), + outputs: vec![1], ..Default::default() - }; + }]); graph.build_inputs(); graph.build_outputs(); @@ -860,19 +838,15 @@ fn local_placer_rejects_multi_output_sequential_primitives_until_edges_are_port_ #[test] fn local_placer_rejects_sequential_primitives_without_macro_candidates() { - let mut graph = Graph { - nodes: vec![GraphNode { - id: 0, - kind: GraphNodeKind::Sequential(SequentialPrimitive::new( - SequentialType::DFlipFlop, - Vec::new(), - vec!["q".to_owned()], - )), - outputs: vec![1], - ..Default::default() - }], + let mut graph = Graph::from_nodes(vec![GraphNode { + kind: GraphNodeKind::Sequential(SequentialPrimitive::new( + SequentialType::DFlipFlop, + Vec::new(), + vec!["q".to_owned()], + )), + outputs: vec![1], ..Default::default() - }; + }]); graph.build_inputs(); graph.build_outputs(); @@ -888,8 +862,8 @@ fn local_placer_rejects_sequential_primitives_without_macro_candidates() { #[test] fn sequential_macro_generation_registers_output_port_positions() { + let node_id = 7; let node = GraphNode { - id: 7, kind: GraphNodeKind::Sequential(SequentialPrimitive::new( SequentialType::RsLatch, Vec::new(), @@ -900,18 +874,19 @@ fn sequential_macro_generation_registers_output_port_positions() { let GraphNodeKind::Sequential(sequential) = &node.kind else { panic!("expected sequential node"); }; + let node_ref = GraphNodeRef::new(node_id, &node); let generated = generate_sequential_macro_routes( &config(1), - &node, + node_ref, sequential, &World3D::new(DimSize(8, 8, 4)), &PlacementState::default(), ); assert!(!generated.is_empty()); - assert!(generated[0].1.node_position(node.id).is_some()); - assert!(generated[0].1.port_position(node.id, "q").is_some()); + assert!(generated[0].1.node_position(node_id).is_some()); + assert!(generated[0].1.port_position(node_id, "q").is_some()); } #[test] @@ -934,16 +909,16 @@ fn sequential_macro_routes_input_ports_from_existing_sources() { vec!["q".to_owned()], ); let node = GraphNode { - id: 2, kind: GraphNodeKind::Sequential(sequential.clone()), inputs: vec![0, 1], ..Default::default() }; + let node_ref = GraphNodeRef::new(2, &node); let candidate = SequentialMacro::candidates(&sequential).remove(0); let placed = place_sequential_macro(&world, &candidate, Position(2, 1, 0)).unwrap(); let state = [(0, source_s), (1, source_r)].into_iter().collect(); - let routed = route_sequential_inputs(&config(4), &node, &sequential, &state, &placed); + let routed = route_sequential_inputs(&config(4), node_ref, &sequential, &state, &placed); assert!(!routed.is_empty()); } diff --git a/src/transform/world.rs b/src/transform/world.rs index db57a2b..276c052 100644 --- a/src/transform/world.rs +++ b/src/transform/world.rs @@ -83,8 +83,6 @@ impl WorldGraphTransformer { ); } - // make clustered node - let mut next_id = self.graph.graph.max_node_id().unwrap(); for members in group_members.values_mut() { members.sort_unstable(); } @@ -95,10 +93,7 @@ impl WorldGraphTransformer { } for group_id in group_ids.iter().sorted() { - next_id += 1; - let node = GraphNode { - id: next_id, kind: GraphNodeKind::Block(Block { kind: BlockKind::Redstone { on_count: 0, @@ -116,6 +111,7 @@ impl WorldGraphTransformer { ), ..Default::default() }; + let node_id = self.graph.graph.add_node(node); group_inputs[group_id].iter().for_each(|&conn| { self.graph @@ -123,7 +119,7 @@ impl WorldGraphTransformer { .find_node_by_id_mut(conn) .unwrap() .outputs - .push(next_id); + .push(node_id); }); group_outputs[group_id].iter().for_each(|&conn| { self.graph @@ -131,11 +127,10 @@ impl WorldGraphTransformer { .find_node_by_id_mut(conn) .unwrap() .inputs - .push(next_id); + .push(node_id); }); - self.graph.routings.insert(node.id); - self.graph.graph.nodes.push(node); + self.graph.routings.insert(node_id); } self.graph.graph.build_inputs(); diff --git a/src/transform/world_to_logic.rs b/src/transform/world_to_logic.rs index d23f52e..eabdcf8 100644 --- a/src/transform/world_to_logic.rs +++ b/src/transform/world_to_logic.rs @@ -65,7 +65,7 @@ impl WorldToLogicTransformer { } pub fn transform(mut self) -> eyre::Result { - self.transform_inner(true) + self.transform_inner() } pub fn positions(&self) -> &HashMap { @@ -77,18 +77,24 @@ impl WorldToLogicTransformer { } pub fn transform_preserving_node_ids(mut self) -> eyre::Result { - self.transform_inner(false) + self.transform_inner() } - fn transform_inner(&mut self, rebuild_node_ids: bool) -> eyre::Result { + fn transform_inner(&mut self) -> eyre::Result { let mut new_in_id = HashMap::new(); - let mut nodes = Vec::new(); + let mut graph = Graph::from_nodes_with_ids( + self.graph + .graph + .nodes + .iter() + .map(|node| (node.id, GraphNode::default())) + .collect(), + ); - let mut next_new_id = self.graph.graph.max_node_id().unwrap(); let mut input_count = 0; for id in self.graph.graph.topological_order() { - let node = self.graph.graph.find_and_remove_node_by_id(id).unwrap(); + let node = self.graph.graph.find_node_by_id(id).unwrap().clone_node(); let inputs = node .inputs @@ -98,76 +104,62 @@ impl WorldToLogicTransformer { .collect(); let tag = transformed_tag(id, &node.tag); - let new_nodes = match node.kind { - GraphNodeKind::Sequential(sequential) => { - vec![GraphNode { - id: node.id, - kind: GraphNodeKind::Sequential(sequential), - inputs, - outputs: node.outputs, - tag, - }] - } + let new_node = match node.kind { + GraphNodeKind::Sequential(sequential) => GraphNode { + kind: GraphNodeKind::Sequential(sequential), + inputs, + outputs: node.outputs, + tag, + }, GraphNodeKind::Block(block) => match block.kind { BlockKind::Switch { .. } => { input_count += 1; - vec![GraphNode { - id: node.id, + GraphNode { kind: GraphNodeKind::Input(format!("#{}", input_count)), inputs, outputs: node.outputs, tag, - }] - } - BlockKind::Redstone { .. } | BlockKind::Repeater { .. } => { - vec![GraphNode { - id: node.id, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::Or, - }), - inputs, - outputs: node.outputs, - tag, - }] + } } + BlockKind::Redstone { .. } | BlockKind::Repeater { .. } => GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::Or, + }), + inputs, + outputs: node.outputs, + tag, + }, BlockKind::Torch { .. } => { if node.inputs.len() == 1 { - vec![GraphNode { - id: node.id, + GraphNode { kind: GraphNodeKind::Logic(Logic { logic_type: LogicType::Not, }), inputs, outputs: node.outputs, tag, - }] + } } else { - next_new_id += 1; - - let or_node = GraphNode { - id: node.id, - kind: GraphNodeKind::Logic(Logic { - logic_type: LogicType::Or, - }), - inputs, - outputs: vec![next_new_id], - tag: tag.clone(), - }; - - let not_node = GraphNode { - id: next_new_id, + let not_node_id = graph.add_node(GraphNode { kind: GraphNodeKind::Logic(Logic { logic_type: LogicType::Not, }), - inputs: vec![or_node.id], + inputs: vec![id], outputs: node.outputs.clone(), - tag, - }; + tag: tag.clone(), + }); - new_in_id.insert(or_node.id, next_new_id); + new_in_id.insert(id, not_node_id); - vec![or_node, not_node] + GraphNode { + kind: GraphNodeKind::Logic(Logic { + logic_type: LogicType::Or, + }), + inputs, + outputs: vec![not_node_id], + tag: tag.clone(), + } } } _ => todo!(), @@ -175,22 +167,12 @@ impl WorldToLogicTransformer { _ => todo!(), }; - nodes.extend(new_nodes); + *graph.find_node_by_id_mut(id).unwrap() = new_node; } - let mut graph = Graph { - nodes, - ..Default::default() - }; - - if rebuild_node_ids { - graph = graph.rebuild_node_ids(); - } else { - graph.nodes.sort_by_key(|node| node.id); - graph.build_outputs(); - graph.build_producers(); - graph.build_consumers(); - } + graph.build_outputs(); + graph.build_producers(); + graph.build_consumers(); Ok(LogicGraph { graph }) } @@ -361,4 +343,61 @@ mod tests { Ok(()) } + + #[test] + fn world_to_logic_transform_keeps_sparse_ids_and_appends_synthetic_nodes() -> eyre::Result<()> { + let switch = Block { + kind: BlockKind::Switch { is_on: false }, + direction: Direction::None, + }; + let torch = Block { + kind: BlockKind::Torch { is_on: true }, + direction: Direction::Bottom, + }; + let mut graph = crate::graph::Graph::from_nodes_with_ids(vec![ + ( + 10, + crate::graph::GraphNode { + kind: crate::graph::GraphNodeKind::Block(switch), + outputs: vec![30], + ..Default::default() + }, + ), + ( + 20, + crate::graph::GraphNode { + kind: crate::graph::GraphNodeKind::Block(switch), + outputs: vec![30], + ..Default::default() + }, + ), + ( + 30, + crate::graph::GraphNode { + kind: crate::graph::GraphNodeKind::Block(torch), + inputs: vec![10, 20], + ..Default::default() + }, + ), + ]); + graph.build_inputs(); + graph.build_outputs(); + graph.build_producers(); + graph.build_consumers(); + + let world_graph = crate::graph::world::WorldGraph { + graph, + ..Default::default() + }; + let logic = WorldToLogicTransformer::new(world_graph, true)?.transform()?; + + assert_eq!( + logic.graph.nodes.iter().map(|node| node.id).collect_vec(), + vec![10, 20, 30, 31] + ); + assert_eq!(logic.graph.find_node_by_id(30).unwrap().outputs, vec![31]); + assert_eq!(logic.graph.find_node_by_id(31).unwrap().inputs, vec![30]); + + Ok(()) + } }