use std::collections::HashMap;
use crate::error::ModelError;
use crate::job;
type NodeIndex = usize;
#[derive(Debug)]
pub struct StepDependencyEdge {
pub origin: NodeIndex,
pub dependent: NodeIndex,
}
#[derive(Debug)]
pub struct StepDependencyNode {
pub step_index: usize,
pub name: String,
pub in_edges: Vec<usize>,
pub out_edges: Vec<usize>,
}
#[derive(Debug)]
pub struct StepDependencyGraph {
nodes: Vec<StepDependencyNode>,
edges: Vec<StepDependencyEdge>,
name_to_index: HashMap<String, NodeIndex>,
}
impl StepDependencyGraph {
pub fn new(job: &job::Job) -> Result<Self, ModelError> {
let name_to_index: HashMap<String, NodeIndex> = job
.steps
.iter()
.enumerate()
.map(|(i, s)| (s.name.clone(), i))
.collect();
let mut nodes: Vec<StepDependencyNode> = job
.steps
.iter()
.enumerate()
.map(|(i, s)| StepDependencyNode {
step_index: i,
name: s.name.clone(),
in_edges: Vec::new(),
out_edges: Vec::new(),
})
.collect();
let mut edges = Vec::new();
for (dep_idx, step) in job.steps.iter().enumerate() {
if let Some(deps) = &step.dependencies {
for dep in deps {
let origin_idx = *name_to_index.get(&dep.depends_on).ok_or_else(|| {
ModelError::DecodeValidation(format!(
"Step '{}' depends on unknown step '{}'",
step.name, dep.depends_on
))
})?;
let edge_idx = edges.len();
edges.push(StepDependencyEdge {
origin: origin_idx,
dependent: dep_idx,
});
nodes[dep_idx].in_edges.push(edge_idx);
nodes[origin_idx].out_edges.push(edge_idx);
}
}
}
Ok(Self {
nodes,
edges,
name_to_index,
})
}
pub fn step_node(&self, name: &str) -> Option<&StepDependencyNode> {
self.name_to_index.get(name).map(|&i| &self.nodes[i])
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub fn edge(&self, index: usize) -> Option<&StepDependencyEdge> {
self.edges.get(index)
}
pub fn node(&self, index: usize) -> Option<&StepDependencyNode> {
self.nodes.get(index)
}
pub fn max_indegree(&self) -> usize {
self.nodes
.iter()
.map(|n| n.in_edges.len())
.max()
.unwrap_or(0)
}
pub fn max_outdegree(&self) -> usize {
self.nodes
.iter()
.map(|n| n.out_edges.len())
.max()
.unwrap_or(0)
}
pub fn topo_sorted(&self) -> Result<Vec<usize>, ModelError> {
let n = self.nodes.len();
let mut state = vec![0u8; n];
let mut result = Vec::with_capacity(n);
for i in 0..n {
if state[i] != 0 {
continue;
}
let mut stack: Vec<NodeIndex> = vec![i];
while let Some(&top) = stack.last() {
match state[top] {
2 => {
stack.pop();
}
1 => {
state[top] = 2;
result.push(top);
stack.pop();
}
_ => {
state[top] = 1;
let mut dep_indices: Vec<NodeIndex> = self.nodes[top]
.in_edges
.iter()
.map(|&e| self.edges[e].origin)
.collect();
dep_indices.sort_unstable_by(|a, b| b.cmp(a));
for dep in dep_indices {
match state[dep] {
2 => {} 1 => {
let cycle: Vec<&str> = stack
.iter()
.filter(|&&idx| state[idx] == 1)
.map(|&idx| self.nodes[idx].name.as_str())
.collect();
let dep_name = &self.nodes[dep].name;
return Err(ModelError::DecodeValidation(format!(
"A circular dependency was found in the step dependency graph:\n{} -> {}",
cycle.join(" -> "), dep_name
)));
}
_ => stack.push(dep),
}
}
}
}
}
}
Ok(result)
}
pub fn topo_sorted_names(&self) -> Result<Vec<String>, ModelError> {
self.topo_sorted().map(|indices| {
indices
.into_iter()
.map(|i| self.nodes[i].name.clone())
.collect()
})
}
}