1pub mod debug;
8mod diagnostics;
9mod graph;
10pub mod helpers;
11mod passes;
12
13pub use diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSpan};
14pub use graph::{
15 ComputeAffinity, DEFAULT_PLAN_VERSION, Edge, EdgeBufferInfo, ExecutionPlan, GpuSegment, Graph,
16 NodeInstance, NodeRef, PortRef, StableHash,
17};
18pub use passes::{PlannerConfig, PlannerInput, PlannerOutput, build_plan};
19
20#[cfg(test)]
21mod tests {
22 use super::*;
23 use daedalus_data::model::{TypeExpr, ValueType};
24 use daedalus_registry::store::NodeDescriptorBuilder;
25 use daedalus_registry::store::Registry;
26
27 #[test]
28 fn stable_hash_changes_with_edges() {
29 let mut graph = Graph::default();
30 graph.nodes.push(NodeInstance {
31 id: daedalus_registry::ids::NodeId::new("n1"),
32 bundle: None,
33 label: None,
34 inputs: vec![],
35 outputs: vec![],
36 compute: ComputeAffinity::CpuOnly,
37 const_inputs: vec![],
38 sync_groups: vec![],
39 metadata: Default::default(),
40 });
41 graph.nodes.push(NodeInstance {
42 id: daedalus_registry::ids::NodeId::new("n2"),
43 bundle: None,
44 label: None,
45 inputs: vec![],
46 outputs: vec![],
47 compute: ComputeAffinity::CpuOnly,
48 const_inputs: vec![],
49 sync_groups: vec![],
50 metadata: Default::default(),
51 });
52 let g1 = graph.clone();
53 let p1 = build_plan(
54 PlannerInput {
55 graph: g1,
56 registry: &Registry::new(),
57 },
58 PlannerConfig::default(),
59 )
60 .plan;
61
62 graph.edges.push(Edge {
63 from: PortRef {
64 node: NodeRef(0),
65 port: "out".into(),
66 },
67 to: PortRef {
68 node: NodeRef(1),
69 port: "in".into(),
70 },
71 metadata: Default::default(),
72 });
73 let p2 = build_plan(
74 PlannerInput {
75 graph,
76 registry: &Registry::new(),
77 },
78 PlannerConfig::default(),
79 )
80 .plan;
81
82 assert_ne!(p1.hash, p2.hash);
83 }
84
85 #[test]
86 fn reports_missing_node_and_ports_and_converter_gap() {
87 let mut registry = Registry::new();
89 let a = NodeDescriptorBuilder::new("a")
90 .output("out", TypeExpr::Scalar(ValueType::Int))
91 .build()
92 .unwrap();
93 registry.register_node(a).unwrap();
94 let b = NodeDescriptorBuilder::new("b")
95 .input("in", TypeExpr::Scalar(ValueType::Bool))
96 .build()
97 .unwrap();
98 registry.register_node(b).unwrap();
99
100 let mut graph = Graph::default();
101 graph.nodes.push(NodeInstance {
102 id: daedalus_registry::ids::NodeId::new("a"),
103 bundle: None,
104 label: None,
105 inputs: vec![],
106 outputs: vec![],
107 compute: ComputeAffinity::CpuOnly,
108 const_inputs: vec![],
109 sync_groups: vec![],
110 metadata: Default::default(),
111 });
112 graph.nodes.push(NodeInstance {
113 id: daedalus_registry::ids::NodeId::new("b"),
114 bundle: None,
115 label: None,
116 inputs: vec![],
117 outputs: vec![],
118 compute: ComputeAffinity::CpuOnly,
119 const_inputs: vec![],
120 sync_groups: vec![],
121 metadata: Default::default(),
122 });
123 graph.edges.push(Edge {
125 from: PortRef {
126 node: NodeRef(0),
127 port: "missing".into(),
128 },
129 to: PortRef {
130 node: NodeRef(1),
131 port: "in".into(),
132 },
133 metadata: Default::default(),
134 });
135
136 let out = build_plan(
137 PlannerInput {
138 graph,
139 registry: ®istry,
140 },
141 PlannerConfig::default(),
142 );
143
144 assert!(
146 out.diagnostics
147 .iter()
148 .any(|d| matches!(d.code, DiagnosticCode::PortMissing)
149 && d.span.node.as_deref() == Some("a"))
150 );
151
152 let mut registry2 = Registry::new();
154 let a2 = NodeDescriptorBuilder::new("a")
155 .output("out", TypeExpr::Scalar(ValueType::Int))
156 .build()
157 .unwrap();
158 registry2.register_node(a2).unwrap();
159 let b2 = NodeDescriptorBuilder::new("b")
160 .input("in", TypeExpr::Scalar(ValueType::Bool))
161 .build()
162 .unwrap();
163 registry2.register_node(b2).unwrap();
164 let mut graph2 = Graph::default();
165 graph2.nodes.push(NodeInstance {
166 id: daedalus_registry::ids::NodeId::new("a"),
167 bundle: None,
168 label: None,
169 inputs: vec![],
170 outputs: vec![],
171 compute: ComputeAffinity::CpuOnly,
172 const_inputs: vec![],
173 sync_groups: vec![],
174 metadata: Default::default(),
175 });
176 graph2.nodes.push(NodeInstance {
177 id: daedalus_registry::ids::NodeId::new("b"),
178 bundle: None,
179 label: None,
180 inputs: vec![],
181 outputs: vec![],
182 compute: ComputeAffinity::CpuOnly,
183 const_inputs: vec![],
184 sync_groups: vec![],
185 metadata: Default::default(),
186 });
187 graph2.edges.push(Edge {
188 from: PortRef {
189 node: NodeRef(0),
190 port: "out".into(),
191 },
192 to: PortRef {
193 node: NodeRef(1),
194 port: "in".into(),
195 },
196 metadata: Default::default(),
197 });
198
199 let out2 = build_plan(
200 PlannerInput {
201 graph: graph2,
202 registry: ®istry2,
203 },
204 PlannerConfig::default(),
205 );
206
207 assert!(
208 out2.diagnostics
209 .iter()
210 .any(|d| matches!(d.code, DiagnosticCode::ConverterMissing))
211 );
212
213 struct IntToBool;
215 impl daedalus_data::convert::Converter for IntToBool {
216 fn id(&self) -> daedalus_data::convert::ConverterId {
217 daedalus_data::convert::ConverterId("int_to_bool".into())
218 }
219 fn input(&self) -> &TypeExpr {
220 static TY: once_cell::sync::Lazy<TypeExpr> =
221 once_cell::sync::Lazy::new(|| TypeExpr::Scalar(ValueType::Int));
222 &TY
223 }
224 fn output(&self) -> &TypeExpr {
225 static TY: once_cell::sync::Lazy<TypeExpr> =
226 once_cell::sync::Lazy::new(|| TypeExpr::Scalar(ValueType::Bool));
227 &TY
228 }
229 fn convert(
230 &self,
231 _value: daedalus_data::model::Value,
232 ) -> Result<daedalus_data::model::Value, daedalus_data::errors::DataError> {
233 Ok(daedalus_data::model::Value::Bool(true))
234 }
235 fn cost(&self) -> u64 {
236 1
237 }
238 }
239
240 let mut registry3 = Registry::new();
241 let a3 = NodeDescriptorBuilder::new("a")
242 .output("out", TypeExpr::Scalar(ValueType::Int))
243 .build()
244 .unwrap();
245 registry3.register_node(a3).unwrap();
246 let b3 = NodeDescriptorBuilder::new("b")
247 .input("in", TypeExpr::Scalar(ValueType::Bool))
248 .build()
249 .unwrap();
250 registry3.register_node(b3).unwrap();
251 registry3
252 .register_converter(Box::new(IntToBool))
253 .expect("converter registers");
254
255 let mut graph3 = Graph::default();
256 graph3.nodes.push(NodeInstance {
257 id: daedalus_registry::ids::NodeId::new("a"),
258 bundle: None,
259 label: None,
260 inputs: vec![],
261 outputs: vec![],
262 compute: ComputeAffinity::CpuOnly,
263 const_inputs: vec![],
264 sync_groups: vec![],
265 metadata: Default::default(),
266 });
267 graph3.nodes.push(NodeInstance {
268 id: daedalus_registry::ids::NodeId::new("b"),
269 bundle: None,
270 label: None,
271 inputs: vec![],
272 outputs: vec![],
273 compute: ComputeAffinity::CpuOnly,
274 const_inputs: vec![],
275 sync_groups: vec![],
276 metadata: Default::default(),
277 });
278 graph3.edges.push(Edge {
279 from: PortRef {
280 node: NodeRef(0),
281 port: "out".into(),
282 },
283 to: PortRef {
284 node: NodeRef(1),
285 port: "in".into(),
286 },
287 metadata: Default::default(),
288 });
289
290 let out3 = build_plan(
291 PlannerInput {
292 graph: graph3,
293 registry: ®istry3,
294 },
295 PlannerConfig::default(),
296 );
297
298 assert!(
299 !out3
300 .diagnostics
301 .iter()
302 .any(|d| matches!(d.code, DiagnosticCode::ConverterMissing))
303 );
304 }
305
306 #[test]
307 fn detects_cycle_in_align() {
308 let mut registry = Registry::new();
309 let node_desc = NodeDescriptorBuilder::new("n")
310 .input("in", TypeExpr::Scalar(ValueType::Int))
311 .output("out", TypeExpr::Scalar(ValueType::Int))
312 .build()
313 .unwrap();
314 registry.register_node(node_desc).unwrap();
315
316 let mut graph = Graph::default();
317 graph.nodes.push(NodeInstance {
318 id: daedalus_registry::ids::NodeId::new("n"),
319 bundle: None,
320 label: None,
321 inputs: vec![],
322 outputs: vec![],
323 compute: ComputeAffinity::CpuOnly,
324 const_inputs: vec![],
325 sync_groups: vec![],
326 metadata: Default::default(),
327 });
328 graph.nodes.push(NodeInstance {
329 id: daedalus_registry::ids::NodeId::new("n"),
330 bundle: None,
331 label: None,
332 inputs: vec![],
333 outputs: vec![],
334 compute: ComputeAffinity::CpuOnly,
335 const_inputs: vec![],
336 sync_groups: vec![],
337 metadata: Default::default(),
338 });
339
340 graph.edges.push(Edge {
342 from: PortRef {
343 node: NodeRef(0),
344 port: "out".into(),
345 },
346 to: PortRef {
347 node: NodeRef(1),
348 port: "in".into(),
349 },
350 metadata: Default::default(),
351 });
352 graph.edges.push(Edge {
353 from: PortRef {
354 node: NodeRef(1),
355 port: "out".into(),
356 },
357 to: PortRef {
358 node: NodeRef(0),
359 port: "in".into(),
360 },
361 metadata: Default::default(),
362 });
363
364 let out = build_plan(
365 PlannerInput {
366 graph,
367 registry: ®istry,
368 },
369 PlannerConfig {
370 enable_lints: true,
371 ..Default::default()
372 },
373 );
374
375 assert!(
376 out.diagnostics
377 .iter()
378 .any(|d| matches!(d.code, DiagnosticCode::ScheduleConflict))
379 );
380 }
381
382 #[test]
383 fn gpu_required_without_flag_reports() {
384 let mut registry = Registry::new();
385 let node_desc = NodeDescriptorBuilder::new("n")
386 .input("in", TypeExpr::Scalar(ValueType::Int))
387 .output("out", TypeExpr::Scalar(ValueType::Int))
388 .build()
389 .unwrap();
390 registry.register_node(node_desc).unwrap();
391
392 let mut graph = Graph::default();
393 graph.nodes.push(NodeInstance {
394 id: daedalus_registry::ids::NodeId::new("n"),
395 bundle: None,
396 label: None,
397 inputs: vec![],
398 outputs: vec![],
399 compute: ComputeAffinity::GpuRequired,
400 const_inputs: vec![],
401 sync_groups: vec![],
402 metadata: Default::default(),
403 });
404
405 let out = build_plan(
406 PlannerInput {
407 graph,
408 registry: ®istry,
409 },
410 PlannerConfig {
411 enable_gpu: false,
412 ..Default::default()
413 },
414 );
415
416 assert!(
417 out.diagnostics
418 .iter()
419 .any(|d| matches!(d.code, DiagnosticCode::GpuUnsupported))
420 );
421 }
422}