| 164 | // ===================================================================== |
| 165 | |
| 166 | fn schedule<'a, Ctx>(nodes: &[PassNode<'a, Ctx>]) -> Result<Vec<usize>, GraphError> { |
| 167 | let n = nodes.len(); |
| 168 | if n == 0 { return Ok(Vec::new()); } |
| 169 | |
| 170 | // Name → index lookup for `after` / `before` hint resolution. |
| 171 | let mut name_to_idx: HashMap<&'static str, usize> = HashMap::new(); |
| 172 | for (i, node) in nodes.iter().enumerate() { |
| 173 | name_to_idx.insert(node.name, i); |
| 174 | } |
| 175 | |
| 176 | // Build the predecessor set for each node. |
| 177 | // Edge A → B means A must run before B. |
| 178 | let mut preds: Vec<HashSet<usize>> = vec![HashSet::new(); n]; |
| 179 | |
| 180 | // 1) Data-dependency edges: if B reads what A writes, A → B. |
| 181 | // Approximate "writes" as "any write matches any read" — the |
| 182 | // enum comparison treats PassOutput::HdrColor as matching |
| 183 | // PassInput::SceneColor via the `matches_write` helper below. |
| 184 | for b in 0..n { |
| 185 | for read in &nodes[b].reads { |
| 186 | for a in 0..n { |
| 187 | if a == b { continue; } |
| 188 | for write in &nodes[a].writes { |
| 189 | if input_matches_write(read, write) { |
| 190 | preds[b].insert(a); |
| 191 | } |
| 192 | } |
| 193 | } |
| 194 | } |
| 195 | } |
| 196 | // 2) Explicit `after` hints: node B.after = [A] → A → B. |
| 197 | for (b, node) in nodes.iter().enumerate() { |
| 198 | for name in &node.after { |
| 199 | let a = name_to_idx.get(name).ok_or_else(|| GraphError::UnknownNode { |
| 200 | node: node.name.to_string(), |
| 201 | referenced: name.to_string(), |
| 202 | })?; |
| 203 | if *a != b { preds[b].insert(*a); } |
| 204 | } |
| 205 | } |
| 206 | // 3) Explicit `before` hints: node A.before = [B] → A → B. |
| 207 | for (a, node) in nodes.iter().enumerate() { |
| 208 | for name in &node.before { |
| 209 | let b = name_to_idx.get(name).ok_or_else(|| GraphError::UnknownNode { |
| 210 | node: node.name.to_string(), |
| 211 | referenced: name.to_string(), |
| 212 | })?; |
| 213 | if *b != a { preds[*b].insert(a); } |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | // Kahn's algorithm: repeatedly pick a node whose `preds` is empty |
| 218 | // (or whose unsatisfied preds are already in the output), add it, |
| 219 | // strip it from every other preds set. Break ties by declaration |
| 220 | // order to keep the schedule deterministic. |
| 221 | let mut in_degree: Vec<usize> = preds.iter().map(|p| p.len()).collect(); |
| 222 | let mut out: Vec<usize> = Vec::with_capacity(n); |
| 223 | loop { |