1use std::collections::VecDeque;
4
5use indexmap::IndexMap;
6
7use crate::node::Node;
8use crate::port::PortKind;
9
10pub type NodeId = String;
13
14pub type NodeIx = usize;
17
18#[derive(Debug, thiserror::Error)]
19pub enum BuildError {
20 #[error("unknown node reference `{from}` -> `{to}`")]
21 UnknownRef { from: NodeId, to: NodeId },
22
23 #[error("node `{node}` has no input port named `{port}`")]
24 UnknownPort { node: NodeId, port: String },
25
26 #[error("port `{node}.{port}` already connected")]
27 DuplicateEdge { node: NodeId, port: String },
28
29 #[error(
30 "type mismatch on `{node}.{port}`: expected one of [{}], source `{src}` produces {got}",
31 accepts.iter().map(|k| k.to_string()).collect::<Vec<_>>().join(", ")
32 )]
33 TypeMismatch {
34 node: NodeId,
35 port: String,
36 src: NodeId,
37 accepts: Vec<PortKind>,
38 got: PortKind,
39 },
40
41 #[error("required port `{node}.{port}` is not connected")]
42 MissingInput { node: NodeId, port: String },
43
44 #[error("cycle detected involving node `{0}`")]
45 Cycle(NodeId),
46
47 #[error("output node `{0}` is not in the graph")]
48 UnknownOutput(NodeId),
49
50 #[error("graph has no output node")]
51 NoOutput,
52
53 #[error(
54 "output node `{node}` produces `{got}`, but the document output must produce `raster` (canvas-padded). Pipe a sprite through `place`, `tiling`, or `stamp` first."
55 )]
56 OutputKindMismatch { node: NodeId, got: PortKind },
57
58 #[error("required pad ({required}) on node `{node}` exceeds limit ({limit})")]
59 PadExceeded {
60 node: NodeId,
61 required: u32,
62 limit: u32,
63 },
64}
65
66#[derive(Debug, Clone, Copy)]
68pub struct Edge {
69 pub src: NodeIx,
70 pub dst: NodeIx,
71 pub dst_port: usize,
72}
73
74pub struct Graph {
77 nodes: IndexMap<NodeId, Box<dyn Node>>,
78 incoming: Vec<Vec<Option<NodeIx>>>,
81 outgoing: Vec<Vec<NodeIx>>,
83 outgoing_unique: Vec<Vec<NodeIx>>,
87 indegree: Vec<usize>,
89 output: NodeIx,
91 topo: Vec<NodeIx>,
93 output_kinds: Vec<PortKind>,
98}
99
100pub const MAX_PAD: u32 = 256;
103
104pub struct GraphBuilder {
107 nodes: IndexMap<NodeId, Box<dyn Node>>,
108 edges: Vec<EdgeSpec>,
110 output: Option<NodeId>,
111}
112
113struct EdgeSpec {
114 src: NodeId,
115 dst: NodeId,
116 dst_port: String,
117}
118
119impl GraphBuilder {
120 pub fn new() -> Self {
121 Self {
122 nodes: IndexMap::new(),
123 edges: Vec::new(),
124 output: None,
125 }
126 }
127
128 pub fn add_node(&mut self, id: impl Into<NodeId>, node: Box<dyn Node>) -> &mut Self {
129 self.nodes.insert(id.into(), node);
130 self
131 }
132
133 pub fn connect(
134 &mut self,
135 src: impl Into<NodeId>,
136 dst: impl Into<NodeId>,
137 dst_port: impl Into<String>,
138 ) -> &mut Self {
139 self.edges.push(EdgeSpec {
140 src: src.into(),
141 dst: dst.into(),
142 dst_port: dst_port.into(),
143 });
144 self
145 }
146
147 pub fn set_output(&mut self, id: impl Into<NodeId>) -> &mut Self {
148 self.output = Some(id.into());
149 self
150 }
151
152 pub fn build(self) -> Result<Graph, BuildError> {
153 let n = self.nodes.len();
154 let mut incoming: Vec<Vec<Option<NodeIx>>> = self
155 .nodes
156 .values()
157 .map(|node| vec![None; node.inputs().len()])
158 .collect();
159 let mut outgoing: Vec<Vec<NodeIx>> = vec![Vec::new(); n];
160
161 let ix_of = |id: &str| -> Option<NodeIx> { self.nodes.get_index_of(id) };
162
163 for edge in &self.edges {
166 let src_ix = ix_of(&edge.src).ok_or_else(|| BuildError::UnknownRef {
167 from: edge.src.clone(),
168 to: edge.dst.clone(),
169 })?;
170 let dst_ix = ix_of(&edge.dst).ok_or_else(|| BuildError::UnknownRef {
171 from: edge.src.clone(),
172 to: edge.dst.clone(),
173 })?;
174
175 let (_, dst_node) = self
176 .nodes
177 .get_index(dst_ix)
178 .expect("dst_ix came from ix_of and is in range");
179 let port_ix = dst_node
180 .inputs()
181 .iter()
182 .position(|p| p.name == edge.dst_port)
183 .ok_or_else(|| BuildError::UnknownPort {
184 node: edge.dst.clone(),
185 port: edge.dst_port.clone(),
186 })?;
187
188 if incoming[dst_ix][port_ix].is_some() {
189 return Err(BuildError::DuplicateEdge {
190 node: edge.dst.clone(),
191 port: edge.dst_port.clone(),
192 });
193 }
194
195 incoming[dst_ix][port_ix] = Some(src_ix);
196 outgoing[src_ix].push(dst_ix);
197 }
198
199 for (ix, (id, node)) in self.nodes.iter().enumerate() {
201 for (port_ix, port) in node.inputs().iter().enumerate() {
202 if !port.optional && incoming[ix][port_ix].is_none() {
203 return Err(BuildError::MissingInput {
204 node: id.clone(),
205 port: port.name.to_string(),
206 });
207 }
208 }
209 }
210
211 let output_id = self.output.ok_or(BuildError::NoOutput)?;
212 let output_ix = ix_of(&output_id).ok_or(BuildError::UnknownOutput(output_id.clone()))?;
213
214 let topo = topo_sort(n, &incoming, &self.nodes, output_ix)?;
215
216 let mut output_kinds: Vec<PortKind> = vec![PortKind::Raster; n];
220 for &ix in &topo {
221 let (id, node) = self.nodes.get_index(ix).expect("ix from topo is in range");
222 let specs = node.inputs();
223 let mut input_kinds: Vec<Option<PortKind>> = Vec::with_capacity(specs.len());
224 for (port_ix, spec) in specs.iter().enumerate() {
225 match incoming[ix][port_ix] {
226 Some(src_ix) => {
227 let src_kind = output_kinds[src_ix];
228 if !spec.accepts_kind(src_kind) {
229 let (src_id, _) = self
230 .nodes
231 .get_index(src_ix)
232 .expect("src_ix from incoming is in range");
233 return Err(BuildError::TypeMismatch {
234 node: id.clone(),
235 port: spec.name.to_string(),
236 src: src_id.clone(),
237 accepts: spec.accepts.to_vec(),
238 got: src_kind,
239 });
240 }
241 input_kinds.push(Some(src_kind));
242 }
243 None => input_kinds.push(None),
244 }
245 }
246 output_kinds[ix] = node.output(&input_kinds);
247 }
248
249 let output_kind = output_kinds[output_ix];
253 if output_kind != PortKind::Raster {
254 return Err(BuildError::OutputKindMismatch {
255 node: output_id.clone(),
256 got: output_kind,
257 });
258 }
259
260 let mut outgoing_unique = outgoing.clone();
264 for dsts in &mut outgoing_unique {
265 dsts.sort_unstable();
266 dsts.dedup();
267 }
268 let indegree: Vec<usize> = incoming
269 .iter()
270 .map(|ports| {
271 let mut srcs: Vec<NodeIx> = ports.iter().filter_map(|p| *p).collect();
272 srcs.sort_unstable();
273 srcs.dedup();
274 srcs.len()
275 })
276 .collect();
277
278 Ok(Graph {
279 nodes: self.nodes,
280 incoming,
281 outgoing,
282 outgoing_unique,
283 indegree,
284 output: output_ix,
285 topo,
286 output_kinds,
287 })
288 }
289}
290
291impl Default for GraphBuilder {
292 fn default() -> Self {
293 Self::new()
294 }
295}
296
297fn topo_sort(
312 n: usize,
313 incoming: &[Vec<Option<NodeIx>>],
314 nodes: &IndexMap<NodeId, Box<dyn Node>>,
315 output: NodeIx,
316) -> Result<Vec<NodeIx>, BuildError> {
317 let kahn = kahn_order(n, incoming, nodes)?;
318
319 let mut order = Vec::with_capacity(n);
323 let mut seen = vec![false; n];
324 let mut stack: Vec<(NodeIx, usize)> = vec![(output, 0)];
325 seen[output] = true;
326 while let Some((ix, port)) = stack.pop() {
327 match incoming[ix].get(port) {
331 Some(&edge) => {
332 stack.push((ix, port + 1));
333 if let Some(src) = edge {
334 if !seen[src] {
335 seen[src] = true;
336 stack.push((src, 0));
337 }
338 }
339 }
340 None => order.push(ix),
341 }
342 }
343 order.extend(kahn.into_iter().filter(|&ix| !seen[ix]));
346 Ok(order)
347}
348
349fn kahn_order(
350 n: usize,
351 incoming: &[Vec<Option<NodeIx>>],
352 nodes: &IndexMap<NodeId, Box<dyn Node>>,
353) -> Result<Vec<NodeIx>, BuildError> {
354 let mut indegree: Vec<usize> = incoming
356 .iter()
357 .map(|ports| {
358 let mut srcs: Vec<NodeIx> = ports.iter().filter_map(|p| *p).collect();
359 srcs.sort_unstable();
360 srcs.dedup();
361 srcs.len()
362 })
363 .collect();
364
365 let mut rev: Vec<Vec<NodeIx>> = vec![Vec::new(); n];
367 for (dst, ports) in incoming.iter().enumerate() {
368 let mut srcs: Vec<NodeIx> = ports.iter().filter_map(|p| *p).collect();
369 srcs.sort_unstable();
370 srcs.dedup();
371 for src in srcs {
372 rev[src].push(dst);
373 }
374 }
375
376 let mut queue: VecDeque<NodeIx> = (0..n).filter(|&i| indegree[i] == 0).collect();
377 let mut order = Vec::with_capacity(n);
378 while let Some(ix) = queue.pop_front() {
379 order.push(ix);
380 for &dst in &rev[ix] {
381 indegree[dst] -= 1;
382 if indegree[dst] == 0 {
383 queue.push_back(dst);
384 }
385 }
386 }
387
388 if order.len() != n {
389 let bad = (0..n)
393 .find(|&i| indegree[i] != 0)
394 .expect("order.len() != n implies some indegree is non-zero");
395 let (id, _) = nodes.get_index(bad).expect("bad < n is within nodes range");
396 return Err(BuildError::Cycle(id.clone()));
397 }
398 Ok(order)
399}
400
401impl std::fmt::Debug for Graph {
402 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403 let ids: Vec<&str> = self.nodes.keys().map(String::as_str).collect();
404 f.debug_struct("Graph")
405 .field("nodes", &ids)
406 .field("output", &self.node_id(self.output))
407 .field(
408 "topo",
409 &self
410 .topo
411 .iter()
412 .map(|&i| self.node_id(i))
413 .collect::<Vec<_>>(),
414 )
415 .finish()
416 }
417}
418
419impl Graph {
420 pub fn len(&self) -> usize {
422 self.nodes.len()
423 }
424
425 pub fn is_empty(&self) -> bool {
426 self.nodes.is_empty()
427 }
428
429 pub fn output(&self) -> NodeIx {
430 self.output
431 }
432
433 pub fn topo_order(&self) -> &[NodeIx] {
435 &self.topo
436 }
437
438 pub fn asset_inputs(&self) -> std::collections::BTreeSet<String> {
445 self.nodes.values().flat_map(|n| n.asset_inputs()).collect()
446 }
447
448 pub fn node(&self, ix: NodeIx) -> &dyn Node {
449 self.nodes
450 .get_index(ix)
451 .expect("NodeIx is always within self.nodes range")
452 .1
453 .as_ref()
454 }
455
456 pub fn node_id(&self, ix: NodeIx) -> &str {
457 self.nodes
458 .get_index(ix)
459 .expect("NodeIx is always within self.nodes range")
460 .0
461 }
462
463 pub fn index_of(&self, id: &str) -> Option<NodeIx> {
465 self.nodes.get_index_of(id)
466 }
467
468 pub fn upstream(&self, ix: NodeIx) -> impl Iterator<Item = NodeIx> + '_ {
470 let mut srcs: Vec<NodeIx> = self.incoming[ix].iter().filter_map(|p| *p).collect();
471 srcs.sort_unstable();
472 srcs.dedup();
473 srcs.into_iter()
474 }
475
476 pub fn downstream(&self, ix: NodeIx) -> &[NodeIx] {
479 &self.outgoing[ix]
480 }
481
482 pub fn downstream_unique(&self, ix: NodeIx) -> &[NodeIx] {
484 &self.outgoing_unique[ix]
485 }
486
487 pub fn indegree(&self, ix: NodeIx) -> usize {
489 self.indegree[ix]
490 }
491
492 pub fn incoming(&self, ix: NodeIx, port_ix: usize) -> Option<NodeIx> {
494 self.incoming[ix][port_ix]
495 }
496
497 pub fn output_kind(&self, ix: NodeIx) -> PortKind {
500 self.output_kinds[ix]
501 }
502
503 pub fn compute_pad(&self, doc_pad: u32) -> Result<Vec<u32>, BuildError> {
506 let mut required = vec![0u32; self.len()];
507 required[self.output] = doc_pad;
508 for &ix in self.topo.iter().rev() {
509 let down = required[ix];
510 let up = self.node(ix).required_pad(down);
511 if up > MAX_PAD {
512 return Err(BuildError::PadExceeded {
513 node: self.node_id(ix).to_string(),
514 required: up,
515 limit: MAX_PAD,
516 });
517 }
518 for src in self.upstream(ix) {
519 required[src] = required[src].max(up);
520 }
521 }
522 Ok(required)
523 }
524}