1use crate::{Catalog, Graph, Host, NodeId, Placement};
41use std::fmt;
42
43#[derive(Debug, Clone, PartialEq, Eq)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub enum Plan {
49 Empty,
51 Execute {
53 node: NodeId,
55 from: Vec<NodeId>,
57 },
58 Sequence(Vec<Plan>),
61 Wave(Vec<Plan>),
65 Remote {
68 host: Host,
70 inner: Box<Plan>,
72 },
73}
74
75pub fn compile(graph: &Graph, catalog: &Catalog) -> Result<Plan, CompileError> {
79 if graph.is_empty() {
80 return Ok(Plan::Empty);
81 }
82
83 let order = graph.topological_sort();
84 for node in &order {
85 if catalog.get(node).is_none() {
86 return Err(CompileError::NoImplementation((*node).clone()));
87 }
88 }
89
90 Ok(decompose(graph, &order))
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub struct Step<'p> {
96 pub node: &'p NodeId,
98 pub from: &'p [NodeId],
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum Destination<'p> {
105 Node(&'p NodeId),
107 Away(&'p Host),
109}
110
111impl Plan {
112 pub fn steps(&self) -> impl Iterator<Item = Step<'_>> {
116 Steps { left: vec![self] }
117 }
118
119 pub fn destinations(&self) -> impl Iterator<Item = Destination<'_>> {
124 Destinations { left: vec![self] }
125 }
126}
127
128struct Steps<'p> {
131 left: Vec<&'p Plan>,
132}
133
134impl<'p> Iterator for Steps<'p> {
135 type Item = Step<'p>;
136
137 fn next(&mut self) -> Option<Self::Item> {
138 while let Some(plan) = self.left.pop() {
139 match plan {
140 Plan::Empty => {}
141 Plan::Execute { node, from } => return Some(Step { node, from }),
142 Plan::Sequence(plans) | Plan::Wave(plans) => self.left.extend(plans.iter().rev()),
143 Plan::Remote { inner, .. } => self.left.push(inner),
144 }
145 }
146 None
147 }
148}
149
150struct Destinations<'p> {
153 left: Vec<&'p Plan>,
154}
155
156impl<'p> Iterator for Destinations<'p> {
157 type Item = Destination<'p>;
158
159 fn next(&mut self) -> Option<Self::Item> {
160 while let Some(plan) = self.left.pop() {
161 match plan {
162 Plan::Empty => {}
163 Plan::Execute { node, .. } => return Some(Destination::Node(node)),
164 Plan::Sequence(plans) | Plan::Wave(plans) => self.left.extend(plans.iter().rev()),
165 Plan::Remote { host, .. } => return Some(Destination::Away(host)),
166 }
167 }
168 None
169 }
170}
171
172pub fn distribute(plan: &Plan, placement: &Placement) -> Plan {
176 if placement.is_local() {
177 return plan.clone();
178 }
179 wrap(plan, placement)
180}
181
182enum Where {
184 Nothing,
186 All(Option<Host>),
188 Mixed,
190}
191
192fn wrap(plan: &Plan, placement: &Placement) -> Plan {
193 if matches!(plan, Plan::Remote { .. }) {
194 return plan.clone();
195 }
196 match uniform(plan, placement) {
197 Where::All(Some(host)) => Plan::Remote {
198 host,
199 inner: Box::new(plan.clone()),
200 },
201 Where::All(None) | Where::Nothing => plan.clone(),
202 Where::Mixed => match plan {
203 Plan::Sequence(plans) => Plan::Sequence(runs(plans, placement)),
204 Plan::Wave(branches) => {
207 Plan::Wave(branches.iter().map(|p| wrap(p, placement)).collect())
208 }
209 Plan::Empty | Plan::Execute { .. } | Plan::Remote { .. } => plan.clone(),
210 },
211 }
212}
213
214fn runs(plans: &[Plan], placement: &Placement) -> Vec<Plan> {
217 let mut out: Vec<Plan> = Vec::new();
218 let mut run: Vec<Plan> = Vec::new();
219 let mut destination: Option<Host> = None;
220
221 for plan in plans {
222 let here = match plan {
223 Plan::Remote { .. } => None,
224 _ => match uniform(plan, placement) {
225 Where::All(Some(host)) => Some(host),
226 Where::All(None) | Where::Nothing | Where::Mixed => None,
227 },
228 };
229 if here != destination {
230 close(&mut out, &mut run, destination.take());
231 destination = here;
232 }
233 match destination {
234 Some(_) => run.push(plan.clone()),
235 None => out.push(wrap(plan, placement)),
236 }
237 }
238 close(&mut out, &mut run, destination);
239 out
240}
241
242fn close(out: &mut Vec<Plan>, run: &mut Vec<Plan>, destination: Option<Host>) {
244 let Some(host) = destination else { return };
245 let inner = match run.len() {
248 1 => run.remove(0),
249 _ => Plan::Sequence(std::mem::take(run)),
250 };
251 out.push(Plan::Remote {
252 host,
253 inner: Box::new(inner),
254 });
255}
256
257fn uniform(plan: &Plan, placement: &Placement) -> Where {
259 let places: Vec<Option<Host>> = plan
260 .destinations()
261 .map(|destination| match destination {
262 Destination::Node(node) => placement.host_of(node).cloned(),
263 Destination::Away(host) => Some(host.clone()),
264 })
265 .collect();
266 match places.split_first() {
267 None => Where::Nothing,
268 Some((first, rest)) if rest.iter().all(|host| host == first) => Where::All(first.clone()),
269 Some(_) => Where::Mixed,
270 }
271}
272
273fn decompose<'g>(graph: &'g Graph, nodes: &[&'g NodeId]) -> Plan {
277 match nodes {
278 [] => Plan::Empty,
279 [only] => step(graph, only),
280 _ => {
281 let parts = components(graph, nodes);
282 if parts.len() > 1 {
283 return Plan::Wave(parts.iter().map(|part| decompose(graph, part)).collect());
284 }
285
286 let Some(cut) = series_cut(graph, nodes) else {
287 return Plan::Sequence(nodes.iter().map(|node| step(graph, node)).collect());
289 };
290
291 let mut steps = vec![decompose(graph, &nodes[..cut])];
294 match decompose(graph, &nodes[cut..]) {
295 Plan::Sequence(rest) => steps.extend(rest),
296 other => steps.push(other),
297 }
298 Plan::Sequence(steps)
299 }
300 }
301}
302
303fn step(graph: &Graph, node: &NodeId) -> Plan {
306 Plan::Execute {
307 node: node.clone(),
308 from: graph.predecessors(node).into_iter().cloned().collect(),
309 }
310}
311
312fn components<'g>(graph: &'g Graph, nodes: &[&'g NodeId]) -> Vec<Vec<&'g NodeId>> {
315 let mut unassigned: Vec<bool> = vec![true; nodes.len()];
316 let mut out = Vec::new();
317
318 for start in 0..nodes.len() {
319 if !unassigned[start] {
320 continue;
321 }
322 unassigned[start] = false;
323 let mut group = vec![start];
324 let mut frontier = vec![start];
325
326 while let Some(i) = frontier.pop() {
327 for j in 0..nodes.len() {
328 if unassigned[j] && adjacent(graph, nodes[i], nodes[j]) {
329 unassigned[j] = false;
330 group.push(j);
331 frontier.push(j);
332 }
333 }
334 }
335
336 group.sort_unstable();
337 out.push(group.into_iter().map(|i| nodes[i]).collect());
338 }
339 out
340}
341
342fn adjacent(graph: &Graph, a: &NodeId, b: &NodeId) -> bool {
344 graph.successors(a).contains(&b) || graph.successors(b).contains(&a)
345}
346
347fn series_cut(graph: &Graph, nodes: &[&NodeId]) -> Option<usize> {
349 (1..nodes.len()).find(|cut| is_series_cut(graph, &nodes[..*cut], &nodes[*cut..]))
350}
351
352fn is_series_cut(graph: &Graph, before: &[&NodeId], after: &[&NodeId]) -> bool {
355 let sinks: Vec<&NodeId> = before
356 .iter()
357 .copied()
358 .filter(|node| !graph.successors(node).iter().any(|s| before.contains(s)))
359 .collect();
360 let sources: Vec<&NodeId> = after
361 .iter()
362 .copied()
363 .filter(|node| !graph.predecessors(node).iter().any(|p| after.contains(p)))
364 .collect();
365
366 let crosses_outside_the_ends = before.iter().any(|node| {
367 graph
368 .successors(node)
369 .iter()
370 .any(|succ| after.contains(succ) && !(sinks.contains(node) && sources.contains(succ)))
371 });
372 if crosses_outside_the_ends {
373 return false;
374 }
375
376 sinks.iter().all(|sink| {
377 let onward = graph.successors(sink);
378 sources.iter().all(|source| onward.contains(source))
379 })
380}
381
382#[derive(Debug, Clone, PartialEq, Eq)]
384pub enum CompileError {
385 NoImplementation(NodeId),
387}
388
389impl fmt::Display for CompileError {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 match self {
392 Self::NoImplementation(id) => {
393 write!(f, "node `{id}` has no registered implementation")
394 }
395 }
396 }
397}
398
399impl std::error::Error for CompileError {}