Topo-sort `subsystems` by their declared dependencies. Returns a `Vec ` where each element is an index into `subsystems` and the order represents a valid start sequence (dependencies before dependents). # Errors - `TopoError::UnknownDependency` — a subsystem lists a dependency name that is not present in `subsystems`. - `TopoError::Cycle` — a circular dependency was detected.
(subsystems: &[Arc<dyn ClusterSubsystem>])
| 24 | /// that is not present in `subsystems`. |
| 25 | /// - `TopoError::Cycle` — a circular dependency was detected. |
| 26 | pub fn topo_sort(subsystems: &[Arc<dyn ClusterSubsystem>]) -> Result<Vec<usize>, TopoError> { |
| 27 | // Build name → index map. |
| 28 | let name_to_idx: HashMap<&'static str, usize> = subsystems |
| 29 | .iter() |
| 30 | .enumerate() |
| 31 | .map(|(i, s)| (s.name(), i)) |
| 32 | .collect(); |
| 33 | |
| 34 | // Validate all dependency names and build adjacency (dep → dependent) |
| 35 | // plus in-degree per node. |
| 36 | let n = subsystems.len(); |
| 37 | let mut in_degree = vec![0usize; n]; |
| 38 | // adjacency[i] = list of indices that depend on i (i must start before them) |
| 39 | let mut adjacency: Vec<Vec<usize>> = vec![Vec::new(); n]; |
| 40 | |
| 41 | for (i, subsystem) in subsystems.iter().enumerate() { |
| 42 | for &dep_name in subsystem.dependencies() { |
| 43 | let dep_idx = |
| 44 | name_to_idx |
| 45 | .get(dep_name) |
| 46 | .copied() |
| 47 | .ok_or(TopoError::UnknownDependency { |
| 48 | subsystem: subsystem.name(), |
| 49 | dependency: dep_name, |
| 50 | })?; |
| 51 | // dep_idx must come before i |
| 52 | adjacency[dep_idx].push(i); |
| 53 | in_degree[i] += 1; |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | // Kahn's algorithm. |
| 58 | let mut queue: VecDeque<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect(); |
| 59 | |
| 60 | let mut order = Vec::with_capacity(n); |
| 61 | |
| 62 | while let Some(idx) = queue.pop_front() { |
| 63 | order.push(idx); |
| 64 | for &dependent in &adjacency[idx] { |
| 65 | in_degree[dependent] -= 1; |
| 66 | if in_degree[dependent] == 0 { |
| 67 | queue.push_back(dependent); |
| 68 | } |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | if order.len() != n { |
| 73 | // Some nodes still have non-zero in-degree — there is a cycle. |
| 74 | // Collect the names of nodes still in the cycle for diagnostics. |
| 75 | let cycle_names: Vec<&'static str> = in_degree |
| 76 | .iter() |
| 77 | .enumerate() |
| 78 | .filter_map(|(i, °)| { |
| 79 | if deg > 0 { |
| 80 | Some(subsystems[i].name()) |
| 81 | } else { |
| 82 | None |
| 83 | } |