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 | crate::query::Representation::Arguments
188 ) {
189 continue;
190 }
191 match ask.representation.consumes(typing.ty(id).is_closed_form()) {
192 sva_samples::Consumes::ClosedForm if !compose.contains(&id) => compose.push(id),
193 sva_samples::Consumes::ClosedForm => {}
194 _ => {
195 wanted.insert(id);
196 }
197 }
198 if let crate::query::Representation::Ledger { depth } = ask.representation {
199 attributed(typing, id, depth, &mut wanted);
200 }
201 }
202 if audio || !wanted.is_empty() {
203 wanted.insert(root);
204 }
205 compose.retain(|id| !wanted.contains(id));
206 Schedule {
207 wanted: wanted.into_iter().collect(),
208 compose,
209 }
210}
211
212fn attributed(typing: &Typing, id: NodeId, depth: usize, wanted: &mut BTreeSet<NodeId>) {
214 let mut seen = BTreeSet::from([id]);
216 let mut level = vec![id];
217 for _ in 0..depth {
218 let mut next = Vec::new();
219 while let Some(held) = level.pop() {
220 for operand in read_operands(typing, held) {
221 if !seen.insert(operand) {
222 continue;
223 }
224 wanted.insert(operand);
225 match typing.name(operand) == typing.name(held) {
226 true => level.push(operand),
227 false => next.push(operand),
228 }
229 }
230 }
231 level = next;
232 }
233}
234
235pub(crate) fn holds_self(typing: &Typing, id: NodeId, seen: &mut BTreeSet<NodeId>) -> bool {
238 if !seen.insert(id) {
239 return false;
240 }
241 match typing.value(id) {
242 Value::SelfAt { .. } => true,
243 Value::Cast(Cast::Sample, _) | Value::Read { .. } => false,
244 Value::Cast(_, source) => holds_self(typing, *source, seen),
245 Value::Op { args, .. } => args.iter().any(|a| holds_self(typing, *a, seen)),
246 Value::Filter {
247 x, cutoff, q, gain, ..
248 } => [x, cutoff, q, gain]
249 .into_iter()
250 .any(|operand| holds_self(typing, *operand, seen)),
251 Value::Solver { varying, .. } => varying.iter().any(|(_, a)| holds_self(typing, *a, seen)),
252 Value::ClosedForm(_) | Value::Noise(_) => false,
253 }
254}
255
256pub(crate) fn materialized_operands(typing: &Typing, id: NodeId) -> Vec<NodeId> {
259 let sampled = |set: Vec<NodeId>| -> Vec<NodeId> {
260 let mut out = Vec::new();
261 for op in set {
262 if typing.ty(op).is_closed_form() {
263 continue;
264 }
265 let inlined = matches!(
266 typing.value(op),
267 Value::Op { .. } | Value::Filter { .. } | Value::Read { .. }
268 );
269 match inlined || holds_self(typing, op, &mut BTreeSet::new()) {
270 true => out.extend(materialized_operands(typing, op)),
271 false => out.push(op),
272 }
273 }
274 out
275 };
276 match typing.value(id) {
277 Value::ClosedForm(_) | Value::Noise(_) => Vec::new(),
278 Value::SelfAt { at, .. } => sampled(at.moving()),
279 Value::Solver { varying, .. } => sampled(varying.iter().map(|(_, a)| *a).collect()),
280 Value::Cast(Cast::Sample, source) => vec![*source],
281 Value::Read { source, at, .. } => {
282 let mut out = match (at, anywhere(typing, *source)) {
283 (When::Moving(_) | When::Step(_), true) => Vec::new(),
284 _ => vec![*source],
285 };
286 out.extend(sampled(at.moving()));
287 out
288 }
289 Value::Cast(_, source) => sampled(vec![*source]),
290 Value::Op { args, .. } => sampled(args.clone()),
291 Value::Filter {
292 x, cutoff, q, gain, ..
293 } => sampled(vec![*x, *cutoff, *q, *gain]),
294 }
295}
296
297pub(crate) fn anywhere(typing: &Typing, id: NodeId) -> bool {
299 match typing.value(id) {
300 Value::Noise(_) => true,
301 Value::Cast(Cast::Sample, of) => typing.ty(*of).held == Held::Form(Var::T),
302 Value::ClosedForm(form) => form.var == Var::T,
303 _ => false,
304 }
305}
306
307pub(crate) fn read_operands(typing: &Typing, id: NodeId) -> Vec<NodeId> {
309 match typing.value(id) {
310 Value::ClosedForm(form) => crate::refs::nodes_in(&form.body),
311 _ if typing.ty(id).is_closed_form() => {
312 let mut out = Vec::new();
313 reads_under(typing, id, &mut BTreeSet::new(), &mut out);
314 out.dedup();
315 out
316 }
317 _ => materialized_operands(typing, id),
318 }
319}
320
321fn reads_under(typing: &Typing, id: NodeId, seen: &mut BTreeSet<NodeId>, out: &mut Vec<NodeId>) {
323 if !seen.insert(id) {
324 return;
325 }
326 match typing.value(id) {
327 Value::ClosedForm(form) => out.extend(crate::refs::nodes_in(&form.body)),
328 Value::Read { source, .. } => out.push(*source),
329 Value::Cast(_, source) => read_through(typing, *source, seen, out),
330 Value::Op { args, .. } => {
331 for arg in args {
332 read_through(typing, *arg, seen, out);
333 }
334 }
335 Value::Filter {
336 x, cutoff, q, gain, ..
337 } => {
338 for operand in [x, cutoff, q, gain] {
339 read_through(typing, *operand, seen, out);
340 }
341 }
342 Value::Solver { varying, .. } => {
343 for (_, arg) in varying {
344 read_through(typing, *arg, seen, out);
345 }
346 }
347 Value::SelfAt { .. } | Value::Noise(_) => {}
348 }
349}
350
351fn read_through(typing: &Typing, id: NodeId, seen: &mut BTreeSet<NodeId>, out: &mut Vec<NodeId>) {
353 let Value::ClosedForm(_) = typing.value(id) else {
354 return reads_under(typing, id, seen, out);
355 };
356 if seen.insert(id) {
357 out.push(id);
358 }
359}