use sim_kernel::{Cx, Error, Expr, Result, Symbol};
use crate::{
Budget, Cell, Edge, EdgeId, Graph, Node, NodeId, PortRef, Scheduler, TopologyConnection,
capability::topology_write_capability, compile_graph, site::connection_from_graph,
};
mod data;
#[derive(Clone, Debug)]
pub struct TopologyPatch {
pub ops: Vec<PatchOp>,
}
impl TopologyPatch {
pub fn from_expr(expr: &Expr) -> Result<Self> {
let ops = data::parse_patch_ops(expr)?;
if ops.is_empty() {
return Err(patch_error("patch requires at least one operation"));
}
Ok(Self { ops })
}
pub fn to_expr(&self) -> Expr {
data::patch_ops_to_expr(&self.ops)
}
}
#[derive(Clone, Debug)]
pub enum PatchOp {
AddNode(Node),
RemoveNode(NodeId),
ReplaceNode {
id: NodeId,
node: Node,
},
AddEdge {
edge: Edge,
explicit_id: bool,
},
RemoveEdge {
from: PortRef,
to: PortRef,
},
ReplaceEdge {
from: PortRef,
to: PortRef,
edge: Edge,
explicit_id: bool,
},
AddCell(Cell),
SetBudget(Budget),
SetScheduler(Scheduler),
SetMetadata {
key: Symbol,
value: Expr,
},
}
pub fn apply_topology_patch(cx: &mut Cx, source: &Graph, patch: &TopologyPatch) -> Result<Graph> {
cx.require(&topology_write_capability())?;
let graph = apply_topology_patch_ops(source, patch)?;
compile_graph(cx, &graph)?;
Ok(graph)
}
pub fn apply_topology_patch_ops(source: &Graph, patch: &TopologyPatch) -> Result<Graph> {
let mut graph = source.clone();
for op in &patch.ops {
apply_op(&mut graph, op)?;
}
Ok(graph)
}
pub fn patched_connection(
cx: &mut Cx,
source: &Graph,
patch: &TopologyPatch,
) -> Result<TopologyConnection> {
let graph = apply_topology_patch(cx, source, patch)?;
connection_from_graph(cx, &graph)
}
fn apply_op(graph: &mut Graph, op: &PatchOp) -> Result<()> {
match op {
PatchOp::AddNode(node) => graph.nodes.push(node.clone()),
PatchOp::RemoveNode(id) => remove_node(graph, id)?,
PatchOp::ReplaceNode { id, node } => replace_node(graph, id, node)?,
PatchOp::AddEdge { edge, explicit_id } => add_edge(graph, edge, *explicit_id),
PatchOp::RemoveEdge { from, to } => remove_edge(graph, from, to)?,
PatchOp::ReplaceEdge {
from,
to,
edge,
explicit_id,
} => replace_edge(graph, from, to, edge, *explicit_id)?,
PatchOp::AddCell(cell) => graph.cells.push(cell.clone()),
PatchOp::SetBudget(budget) => graph.budget = budget.clone(),
PatchOp::SetScheduler(scheduler) => graph.scheduler = scheduler.clone(),
PatchOp::SetMetadata { key, value } => set_metadata(graph, key, value.clone()),
}
Ok(())
}
fn remove_node(graph: &mut Graph, id: &NodeId) -> Result<()> {
let before = graph.nodes.len();
graph.nodes.retain(|node| &node.id != id);
if graph.nodes.len() == before {
return Err(patch_error(format!(
"remove-node target {} does not exist",
id.as_symbol()
)));
}
Ok(())
}
fn replace_node(graph: &mut Graph, id: &NodeId, node: &Node) -> Result<()> {
if &node.id != id {
return Err(patch_error(format!(
"replace-node replacement id {} does not match target {}",
node.id.as_symbol(),
id.as_symbol()
)));
}
let Some(slot) = graph.nodes.iter_mut().find(|existing| &existing.id == id) else {
return Err(patch_error(format!(
"replace-node target {} does not exist",
id.as_symbol()
)));
};
*slot = node.clone();
Ok(())
}
fn add_edge(graph: &mut Graph, edge: &Edge, explicit_id: bool) {
let mut edge = edge.clone();
if !explicit_id {
edge.id = next_edge_id(graph);
}
graph.edges.push(edge);
}
fn remove_edge(graph: &mut Graph, from: &PortRef, to: &PortRef) -> Result<()> {
let before = graph.edges.len();
graph
.edges
.retain(|edge| &edge.from != from || &edge.to != to);
if graph.edges.len() == before {
return Err(patch_error("remove-edge target does not exist"));
}
Ok(())
}
fn replace_edge(
graph: &mut Graph,
from: &PortRef,
to: &PortRef,
edge: &Edge,
explicit_id: bool,
) -> Result<()> {
let Some(slot) = graph
.edges
.iter_mut()
.find(|existing| &existing.from == from && &existing.to == to)
else {
return Err(patch_error("replace-edge target does not exist"));
};
let mut replacement = edge.clone();
if !explicit_id {
replacement.id = slot.id;
}
*slot = replacement;
Ok(())
}
fn set_metadata(graph: &mut Graph, key: &Symbol, value: Expr) {
if let Some((_, existing)) = graph.metadata.iter_mut().find(|(name, _)| name == key) {
*existing = value;
} else {
graph.metadata.push((key.clone(), value));
}
}
fn next_edge_id(graph: &Graph) -> EdgeId {
EdgeId::new(
graph
.edges
.iter()
.map(|edge| edge.id.0)
.max()
.unwrap_or(0)
.saturating_add(1),
)
}
fn patch_error(message: impl Into<String>) -> Error {
Error::Eval(format!("topology patch error: {}", message.into()))
}