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