1use std::collections::{BTreeMap, BTreeSet, HashMap};
4
5use sva_ast::{Arg, Expr};
6use sva_formula::{Held, NodeId, Var};
7
8use crate::cast::Cast;
9use crate::error::EngineError;
10use crate::instantiate::{Cx, Instances, Node};
11use crate::query::Ask;
12use crate::typing::{Typing, Value, When};
13
14fn direct_refs(inst: &Instances, e: &Expr, cx: Cx, out: &mut Vec<String>) {
16 if inst
17 .follow(e, cx, |e2, cx2| direct_refs(inst, e2, cx2, out))
18 .is_some()
19 {
20 return;
21 }
22 match inst.node(e, cx) {
23 Node::Lit(_) | Node::Name(_) => {}
24 Node::Bin(_, l, r) => {
25 direct_refs(inst, l, cx, out);
26 direct_refs(inst, r, cx, out);
27 }
28 Node::Call { args, .. } => {
29 for a in args {
30 let (Arg::Pos(x) | Arg::Named(_, x)) = a;
31 direct_refs(inst, x, cx, out);
32 }
33 }
34 Node::Read { path, arg, .. } => {
35 out.push(path.to_string());
36 direct_refs(inst, arg, cx, out);
37 }
38 Node::Own { arg, .. } => direct_refs(inst, arg, cx, out),
39 Node::Signal { of, arg, .. } => {
40 direct_refs(inst, of.expr, inst.signal(of, cx), out);
41 direct_refs(inst, arg, cx, out);
42 }
43 }
44}
45
46pub struct Order {
48 pub groups: Vec<Vec<String>>,
49 deps: BTreeMap<String, Vec<String>>,
50}
51
52impl Order {
53 pub fn deps(&self, path: &str) -> &[String] {
54 self.deps.get(path).map_or(&[], Vec::as_slice)
55 }
56
57 pub(crate) fn into_deps(self) -> BTreeMap<String, Vec<String>> {
59 self.deps
60 }
61
62 pub(crate) fn within(&self, kept: &BTreeSet<String>) -> Order {
63 Order {
64 groups: self
65 .groups
66 .iter()
67 .filter(|group| group.iter().all(|path| kept.contains(path)))
68 .cloned()
69 .collect(),
70 deps: self.deps.clone(),
71 }
72 }
73
74 pub fn is_loop(&self, group: &[String]) -> bool {
76 match group {
77 [only] => self.deps(only).iter().any(|d| d == only),
78 _ => true,
79 }
80 }
81}
82
83pub fn direct_deps(inst: &Instances, path: &str) -> Result<Vec<String>, EngineError> {
84 let (e, cx) = inst
85 .at(path)
86 .ok_or_else(|| EngineError::UnknownNode(path.to_string()))?;
87 let mut out = Vec::new();
88 direct_refs(inst, e, cx, &mut out);
89 out.sort();
90 out.dedup();
91 for target in &out {
92 if !inst.holds(target) {
93 return Err(EngineError::UnknownNode(target.clone()));
94 }
95 }
96 Ok(out)
97}
98
99struct Frame {
100 node: String,
101 refs: Vec<String>,
102 idx: usize,
103}
104
105pub fn schedule_from(inst: &Instances, roots: &[String]) -> Result<Order, EngineError> {
107 let mut walk = Walk {
108 inst,
109 deps: BTreeMap::new(),
110 index: HashMap::new(),
111 low: HashMap::new(),
112 open: Vec::new(),
113 next: 0,
114 groups: Vec::new(),
115 };
116 for root in roots {
117 walk.from(root)?;
118 }
119 Ok(Order {
120 groups: walk.groups,
121 deps: walk.deps,
122 })
123}
124
125struct Walk<'a> {
126 inst: &'a Instances<'a>,
127 deps: BTreeMap<String, Vec<String>>,
128 index: HashMap<String, usize>,
129 low: HashMap<String, usize>,
130 open: Vec<String>,
131 next: usize,
132 groups: Vec<Vec<String>>,
133}
134
135impl Walk<'_> {
136 fn from(&mut self, root: &str) -> Result<(), EngineError> {
137 if !self.inst.holds(root) {
138 return Err(EngineError::UnknownNode(root.to_string()));
139 }
140 if self.index.contains_key(root) {
141 return Ok(());
142 }
143 self.index.insert(root.to_string(), self.next);
144 self.low.insert(root.to_string(), self.next);
145 self.next += 1;
146 self.open.push(root.to_string());
147 let seed = direct_deps(self.inst, root)?;
148 self.deps.insert(root.to_string(), seed.clone());
149 let mut stack = vec![Frame {
150 node: root.to_string(),
151 refs: seed,
152 idx: 0,
153 }];
154
155 while let Some(frame) = stack.last_mut() {
156 if frame.idx < frame.refs.len() {
157 let target = frame.refs[frame.idx].clone();
158 let node = frame.node.clone();
159 frame.idx += 1;
160 match self.index.get(&target).copied() {
161 None => {
162 self.index.insert(target.clone(), self.next);
163 self.low.insert(target.clone(), self.next);
164 self.next += 1;
165 self.open.push(target.clone());
166 let refs = direct_deps(self.inst, &target)?;
167 self.deps.insert(target.clone(), refs.clone());
168 stack.push(Frame {
169 node: target,
170 refs,
171 idx: 0,
172 });
173 }
174 Some(at) if self.open.contains(&target) => {
175 let mine = self.low[&node];
176 self.low.insert(node, mine.min(at));
177 }
178 Some(_) => {}
179 }
180 continue;
181 }
182
183 let node = frame.node.clone();
184 let mine = self.low[&node];
185 stack.pop();
186 if let Some(parent) = stack.last() {
187 let above = self.low[&parent.node];
188 self.low.insert(parent.node.clone(), above.min(mine));
189 }
190 if mine == self.index[&node] {
191 let at = self
192 .open
193 .iter()
194 .rposition(|n| *n == node)
195 .expect("a root of its group is still open");
196 let mut group = self.open.split_off(at);
197 group.sort();
198 self.groups.push(group);
199 }
200 }
201 Ok(())
202 }
203}
204
205#[derive(Clone, Debug, Default, PartialEq)]
207pub struct Schedule {
208 pub wanted: Vec<NodeId>,
210 pub compose: Vec<NodeId>,
212}
213
214pub fn plan(typing: &Typing, root: NodeId, asks: &[Ask]) -> Schedule {
216 let mut wanted: BTreeSet<NodeId> = BTreeSet::new();
217 let mut compose: Vec<NodeId> = Vec::new();
218 let audio = asks.is_empty();
219 for ask in asks {
220 let Some(id) = typing.id(&ask.node) else {
221 continue;
222 };
223 if matches!(
225 ask.representation,
226 crate::query::Representation::Bindings
227 | crate::query::Representation::Arguments
228 | crate::query::Representation::Flops
229 ) {
230 continue;
231 }
232 match ask.representation.consumes(typing.ty(id).is_closed_form()) {
233 sva_samples::Consumes::ClosedForm if !compose.contains(&id) => compose.push(id),
234 sva_samples::Consumes::ClosedForm => {}
235 _ => {
236 wanted.insert(id);
237 }
238 }
239 if let crate::query::Representation::Ledger { depth } = ask.representation {
240 attributed(typing, id, depth, &mut wanted);
241 }
242 }
243 if audio || !wanted.is_empty() {
244 wanted.insert(root);
245 }
246 compose.retain(|id| !wanted.contains(id));
247 Schedule {
248 wanted: wanted.into_iter().collect(),
249 compose,
250 }
251}
252
253fn attributed(typing: &Typing, id: NodeId, depth: usize, wanted: &mut BTreeSet<NodeId>) {
255 let mut seen = BTreeSet::from([id]);
257 let mut level = vec![id];
258 for _ in 0..depth {
259 let mut next = Vec::new();
260 while let Some(held) = level.pop() {
261 for operand in read_operands(typing, held) {
262 if !seen.insert(operand) {
263 continue;
264 }
265 wanted.insert(operand);
266 match typing.name(operand) == typing.name(held) {
267 true => level.push(operand),
268 false => next.push(operand),
269 }
270 }
271 }
272 level = next;
273 }
274}
275
276pub(crate) fn holds_self(typing: &Typing, id: NodeId, seen: &mut BTreeSet<NodeId>) -> bool {
279 if !seen.insert(id) {
280 return false;
281 }
282 match typing.value(id) {
283 Value::SelfAt { .. } => true,
284 Value::Cast(Cast::Sample, _) | Value::Read { .. } => false,
285 Value::Cast(_, source) => holds_self(typing, *source, seen),
286 Value::Op { args, .. } => args.iter().any(|a| holds_self(typing, *a, seen)),
287 Value::Filter {
288 x, cutoff, q, gain, ..
289 } => [x, cutoff, q, gain]
290 .into_iter()
291 .any(|operand| holds_self(typing, *operand, seen)),
292 Value::Solver { varying, .. } => varying.iter().any(|(_, a)| holds_self(typing, *a, seen)),
293 Value::ClosedForm(_) | Value::Noise(_) | Value::Stored(_) => false,
294 }
295}
296
297pub(crate) fn materialized_operands(typing: &Typing, id: NodeId) -> Vec<NodeId> {
300 let sampled = |set: Vec<NodeId>| -> Vec<NodeId> {
301 let mut out = Vec::new();
302 for op in set {
303 if typing.ty(op).is_closed_form() {
304 continue;
305 }
306 let inlined = matches!(
307 typing.value(op),
308 Value::Op { .. } | Value::Filter { .. } | Value::Read { .. }
309 );
310 match inlined || holds_self(typing, op, &mut BTreeSet::new()) {
311 true => out.extend(materialized_operands(typing, op)),
312 false => out.push(op),
313 }
314 }
315 out
316 };
317 match typing.value(id) {
318 Value::ClosedForm(_) | Value::Noise(_) | Value::Stored(_) => Vec::new(),
319 Value::SelfAt { at, .. } => sampled(at.moving()),
320 Value::Solver { varying, .. } => sampled(varying.iter().map(|(_, a)| *a).collect()),
321 Value::Cast(Cast::Sample, source) => vec![*source],
322 Value::Read { source, at, .. } => {
323 let mut out = match (at, anywhere(typing, *source)) {
324 (When::Moving(_) | When::Step(_), true) => Vec::new(),
325 _ => vec![*source],
326 };
327 out.extend(sampled(at.moving()));
328 out
329 }
330 Value::Cast(_, source) => sampled(vec![*source]),
331 Value::Op { args, .. } => sampled(args.clone()),
332 Value::Filter {
333 x, cutoff, q, gain, ..
334 } => sampled(vec![*x, *cutoff, *q, *gain]),
335 }
336}
337
338pub(crate) fn anywhere(typing: &Typing, id: NodeId) -> bool {
340 match typing.value(id) {
341 Value::Noise(_) => true,
342 Value::Cast(Cast::Sample, of) => typing.ty(*of).held == Held::Form(Var::T),
343 Value::ClosedForm(form) => form.var == Var::T,
344 _ => false,
345 }
346}
347
348pub(crate) fn read_operands(typing: &Typing, id: NodeId) -> Vec<NodeId> {
350 match typing.value(id) {
351 Value::ClosedForm(form) => crate::refs::nodes_in(&form.body),
352 _ if typing.ty(id).is_closed_form() => {
353 let mut out = Vec::new();
354 reads_under(typing, id, &mut BTreeSet::new(), &mut out);
355 out.dedup();
356 out
357 }
358 _ => materialized_operands(typing, id),
359 }
360}
361
362fn reads_under(typing: &Typing, id: NodeId, seen: &mut BTreeSet<NodeId>, out: &mut Vec<NodeId>) {
364 if !seen.insert(id) {
365 return;
366 }
367 match typing.value(id) {
368 Value::ClosedForm(form) => out.extend(crate::refs::nodes_in(&form.body)),
369 Value::Read { source, .. } => out.push(*source),
370 Value::Cast(_, source) => read_through(typing, *source, seen, out),
371 Value::Op { args, .. } => {
372 for arg in args {
373 read_through(typing, *arg, seen, out);
374 }
375 }
376 Value::Filter {
377 x, cutoff, q, gain, ..
378 } => {
379 for operand in [x, cutoff, q, gain] {
380 read_through(typing, *operand, seen, out);
381 }
382 }
383 Value::Solver { varying, .. } => {
384 for (_, arg) in varying {
385 read_through(typing, *arg, seen, out);
386 }
387 }
388 Value::SelfAt { .. } | Value::Noise(_) | Value::Stored(_) => {}
389 }
390}
391
392fn read_through(typing: &Typing, id: NodeId, seen: &mut BTreeSet<NodeId>, out: &mut Vec<NodeId>) {
394 let Value::ClosedForm(_) = typing.value(id) else {
395 return reads_under(typing, id, seen, out);
396 };
397 if seen.insert(id) {
398 out.push(id);
399 }
400}