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 topo = topo_sort(n, &incoming, &self.nodes)?;
212
213 let mut output_kinds: Vec<PortKind> = vec![PortKind::Raster; n];
217 for &ix in &topo {
218 let (id, node) = self.nodes.get_index(ix).expect("ix from topo is in range");
219 let specs = node.inputs();
220 let mut input_kinds: Vec<Option<PortKind>> = Vec::with_capacity(specs.len());
221 for (port_ix, spec) in specs.iter().enumerate() {
222 match incoming[ix][port_ix] {
223 Some(src_ix) => {
224 let src_kind = output_kinds[src_ix];
225 if !spec.accepts_kind(src_kind) {
226 let (src_id, _) = self
227 .nodes
228 .get_index(src_ix)
229 .expect("src_ix from incoming is in range");
230 return Err(BuildError::TypeMismatch {
231 node: id.clone(),
232 port: spec.name.to_string(),
233 src: src_id.clone(),
234 accepts: spec.accepts.to_vec(),
235 got: src_kind,
236 });
237 }
238 input_kinds.push(Some(src_kind));
239 }
240 None => input_kinds.push(None),
241 }
242 }
243 output_kinds[ix] = node.output(&input_kinds);
244 }
245
246 let output_id = self.output.ok_or(BuildError::NoOutput)?;
247 let output_ix = ix_of(&output_id).ok_or(BuildError::UnknownOutput(output_id.clone()))?;
248 let output_kind = output_kinds[output_ix];
252 if output_kind != PortKind::Raster {
253 return Err(BuildError::OutputKindMismatch {
254 node: output_id.clone(),
255 got: output_kind,
256 });
257 }
258
259 let mut outgoing_unique = outgoing.clone();
263 for dsts in &mut outgoing_unique {
264 dsts.sort_unstable();
265 dsts.dedup();
266 }
267 let indegree: Vec<usize> = incoming
268 .iter()
269 .map(|ports| {
270 let mut srcs: Vec<NodeIx> = ports.iter().filter_map(|p| *p).collect();
271 srcs.sort_unstable();
272 srcs.dedup();
273 srcs.len()
274 })
275 .collect();
276
277 Ok(Graph {
278 nodes: self.nodes,
279 incoming,
280 outgoing,
281 outgoing_unique,
282 indegree,
283 output: output_ix,
284 topo,
285 output_kinds,
286 })
287 }
288}
289
290impl Default for GraphBuilder {
291 fn default() -> Self {
292 Self::new()
293 }
294}
295
296fn topo_sort(
297 n: usize,
298 incoming: &[Vec<Option<NodeIx>>],
299 nodes: &IndexMap<NodeId, Box<dyn Node>>,
300) -> Result<Vec<NodeIx>, BuildError> {
301 let mut indegree: Vec<usize> = incoming
303 .iter()
304 .map(|ports| {
305 let mut srcs: Vec<NodeIx> = ports.iter().filter_map(|p| *p).collect();
306 srcs.sort_unstable();
307 srcs.dedup();
308 srcs.len()
309 })
310 .collect();
311
312 let mut rev: Vec<Vec<NodeIx>> = vec![Vec::new(); n];
314 for (dst, ports) in incoming.iter().enumerate() {
315 let mut srcs: Vec<NodeIx> = ports.iter().filter_map(|p| *p).collect();
316 srcs.sort_unstable();
317 srcs.dedup();
318 for src in srcs {
319 rev[src].push(dst);
320 }
321 }
322
323 let mut queue: VecDeque<NodeIx> = (0..n).filter(|&i| indegree[i] == 0).collect();
324 let mut order = Vec::with_capacity(n);
325 while let Some(ix) = queue.pop_front() {
326 order.push(ix);
327 for &dst in &rev[ix] {
328 indegree[dst] -= 1;
329 if indegree[dst] == 0 {
330 queue.push_back(dst);
331 }
332 }
333 }
334
335 if order.len() != n {
336 let bad = (0..n)
340 .find(|&i| indegree[i] != 0)
341 .expect("order.len() != n implies some indegree is non-zero");
342 let (id, _) = nodes.get_index(bad).expect("bad < n is within nodes range");
343 return Err(BuildError::Cycle(id.clone()));
344 }
345 Ok(order)
346}
347
348impl std::fmt::Debug for Graph {
349 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350 let ids: Vec<&str> = self.nodes.keys().map(String::as_str).collect();
351 f.debug_struct("Graph")
352 .field("nodes", &ids)
353 .field("output", &self.node_id(self.output))
354 .field(
355 "topo",
356 &self
357 .topo
358 .iter()
359 .map(|&i| self.node_id(i))
360 .collect::<Vec<_>>(),
361 )
362 .finish()
363 }
364}
365
366impl Graph {
367 pub fn len(&self) -> usize {
369 self.nodes.len()
370 }
371
372 pub fn is_empty(&self) -> bool {
373 self.nodes.is_empty()
374 }
375
376 pub fn output(&self) -> NodeIx {
377 self.output
378 }
379
380 pub fn topo_order(&self) -> &[NodeIx] {
382 &self.topo
383 }
384
385 pub fn asset_inputs(&self) -> std::collections::BTreeSet<String> {
392 self.nodes.values().flat_map(|n| n.asset_inputs()).collect()
393 }
394
395 pub fn node(&self, ix: NodeIx) -> &dyn Node {
396 self.nodes
397 .get_index(ix)
398 .expect("NodeIx is always within self.nodes range")
399 .1
400 .as_ref()
401 }
402
403 pub fn node_id(&self, ix: NodeIx) -> &str {
404 self.nodes
405 .get_index(ix)
406 .expect("NodeIx is always within self.nodes range")
407 .0
408 }
409
410 pub fn index_of(&self, id: &str) -> Option<NodeIx> {
412 self.nodes.get_index_of(id)
413 }
414
415 pub fn upstream(&self, ix: NodeIx) -> impl Iterator<Item = NodeIx> + '_ {
417 let mut srcs: Vec<NodeIx> = self.incoming[ix].iter().filter_map(|p| *p).collect();
418 srcs.sort_unstable();
419 srcs.dedup();
420 srcs.into_iter()
421 }
422
423 pub fn downstream(&self, ix: NodeIx) -> &[NodeIx] {
426 &self.outgoing[ix]
427 }
428
429 pub fn downstream_unique(&self, ix: NodeIx) -> &[NodeIx] {
431 &self.outgoing_unique[ix]
432 }
433
434 pub fn indegree(&self, ix: NodeIx) -> usize {
436 self.indegree[ix]
437 }
438
439 pub fn incoming(&self, ix: NodeIx, port_ix: usize) -> Option<NodeIx> {
441 self.incoming[ix][port_ix]
442 }
443
444 pub fn output_kind(&self, ix: NodeIx) -> PortKind {
447 self.output_kinds[ix]
448 }
449
450 pub fn compute_pad(&self, doc_pad: u32) -> Result<Vec<u32>, BuildError> {
453 let mut required = vec![0u32; self.len()];
454 required[self.output] = doc_pad;
455 for &ix in self.topo.iter().rev() {
456 let down = required[ix];
457 let up = self.node(ix).required_pad(down);
458 if up > MAX_PAD {
459 return Err(BuildError::PadExceeded {
460 node: self.node_id(ix).to_string(),
461 required: up,
462 limit: MAX_PAD,
463 });
464 }
465 for src in self.upstream(ix) {
466 required[src] = required[src].max(up);
467 }
468 }
469 Ok(required)
470 }
471}