Skip to main content

sva_engine/
schedule.rs

1// Concern: orders nodes dependencies-first and picks which a reading materializes | Non-concern: what a held node contains (render.rs), reading one (refs.rs) | IO: (Instances, asks) -> Schedule
2
3use 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
13/// One dependencies-first walk: the groups, and which are loops.
14pub 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    /// A group of one not reading itself is no loop; any other is.
25    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
43/// A node two roots reach is grouped once, whichever reached it first.
44pub 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
66/// The groups of `region`, dependencies first.
67pub(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/// What a render has to hold, and what it can leave as a closed form.
167#[derive(Clone, Debug, Default, PartialEq)]
168pub struct Schedule {
169    /// What a reading holds for itself, the root among them.
170    pub wanted: Vec<NodeId>,
171    /// The nodes a reading asked a closed form of, none already held.
172    pub compose: Vec<NodeId>,
173}
174
175/// A closed form is held as samples only under a buffer reading, a ledger, or the root.
176pub 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        // FORMAT 14.2: `bindings` and `arguments` are structural; neither composes.
185        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
212/// A ledger names every ref under its target, so each is a buffer of its own.
213fn attributed(typing: &Typing, id: NodeId, depth: usize, wanted: &mut BTreeSet<NodeId>) {
214    // Level by level, as the reading walks: a node is held at its shortest chain's depth.
215    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
235/// A node reading its own output is one renderer, whatever its width: a component on its own
236/// would read the loop at its own width.
237pub(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
256/// Which nodes under `id` a render has to hold before it can hold `id` itself: the buffers its
257/// renderer reads, an operation, filter or read under it being part of that renderer.
258pub(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
297/// Whether a read of `id` has a value at any instant, stored or not.
298pub(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
307/// Every ref one node reads.
308pub(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
321/// A closed-form-typed node that holds no written one still reads refs through its operands.
322fn 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
351/// A written form under an operand is the term read, whatever transform sits between.
352fn 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}