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        if ask.representation.reading().reads == crate::query::Reads::Structure {
185            continue;
186        }
187        match ask.representation.consumes(typing.ty(id).is_closed_form()) {
188            sva_samples::Consumes::ClosedForm if !compose.contains(&id) => compose.push(id),
189            sva_samples::Consumes::ClosedForm => {}
190            _ => {
191                wanted.insert(id);
192            }
193        }
194        if let crate::query::Representation::Ledger { depth } = ask.representation {
195            attributed(typing, id, depth, &mut wanted);
196        }
197    }
198    if audio || !wanted.is_empty() {
199        wanted.insert(root);
200    }
201    compose.retain(|id| !wanted.contains(id));
202    Schedule {
203        wanted: wanted.into_iter().collect(),
204        compose,
205    }
206}
207
208/// A ledger names every ref under its target, so each is a buffer of its own.
209fn attributed(typing: &Typing, id: NodeId, depth: usize, wanted: &mut BTreeSet<NodeId>) {
210    // Level by level, as the reading walks: a node is held at its shortest chain's depth.
211    let mut seen = BTreeSet::from([id]);
212    let mut level = vec![id];
213    for _ in 0..depth {
214        let mut next = Vec::new();
215        while let Some(held) = level.pop() {
216            for operand in read_operands(typing, held) {
217                if !seen.insert(operand) {
218                    continue;
219                }
220                wanted.insert(operand);
221                match typing.name(operand) == typing.name(held) {
222                    true => level.push(operand),
223                    false => next.push(operand),
224                }
225            }
226        }
227        level = next;
228    }
229}
230
231/// A node reading its own output is one renderer, whatever its width: a component on its own
232/// would read the loop at its own width.
233pub(crate) fn holds_self(typing: &Typing, id: NodeId, seen: &mut BTreeSet<NodeId>) -> bool {
234    if !seen.insert(id) {
235        return false;
236    }
237    match typing.value(id) {
238        Value::SelfAt { .. } => true,
239        Value::Cast(Cast::Sample, _) | Value::Read { .. } => false,
240        Value::Cast(_, source) => holds_self(typing, *source, seen),
241        Value::Op { args, .. } => args.iter().any(|a| holds_self(typing, *a, seen)),
242        Value::Filter {
243            x, cutoff, q, gain, ..
244        } => [x, cutoff, q, gain]
245            .into_iter()
246            .any(|operand| holds_self(typing, *operand, seen)),
247        Value::Solver { varying, .. } => varying.iter().any(|(_, a)| holds_self(typing, *a, seen)),
248        Value::ClosedForm(_) | Value::Noise(_) => false,
249    }
250}
251
252/// Which nodes under `id` a render has to hold before it can hold `id` itself: the buffers its
253/// renderer reads, an operation, filter or read under it being part of that renderer.
254pub(crate) fn materialized_operands(typing: &Typing, id: NodeId) -> Vec<NodeId> {
255    let sampled = |set: Vec<NodeId>| -> Vec<NodeId> {
256        let mut out = Vec::new();
257        for op in set {
258            if typing.ty(op).is_closed_form() {
259                continue;
260            }
261            let inlined = matches!(
262                typing.value(op),
263                Value::Op { .. } | Value::Filter { .. } | Value::Read { .. }
264            );
265            match inlined || holds_self(typing, op, &mut BTreeSet::new()) {
266                true => out.extend(materialized_operands(typing, op)),
267                false => out.push(op),
268            }
269        }
270        out
271    };
272    match typing.value(id) {
273        Value::ClosedForm(_) | Value::Noise(_) => Vec::new(),
274        Value::SelfAt { at, .. } => sampled(at.moving()),
275        Value::Solver { varying, .. } => sampled(varying.iter().map(|(_, a)| *a).collect()),
276        Value::Cast(Cast::Sample, source) => vec![*source],
277        Value::Read { source, at, .. } => {
278            let mut out = match (at, anywhere(typing, *source)) {
279                (When::Moving(_) | When::Step(_), true) => Vec::new(),
280                _ => vec![*source],
281            };
282            out.extend(sampled(at.moving()));
283            out
284        }
285        Value::Cast(_, source) => sampled(vec![*source]),
286        Value::Op { args, .. } => sampled(args.clone()),
287        Value::Filter {
288            x, cutoff, q, gain, ..
289        } => sampled(vec![*x, *cutoff, *q, *gain]),
290    }
291}
292
293/// Whether a read of `id` has a value at any instant, stored or not.
294pub(crate) fn anywhere(typing: &Typing, id: NodeId) -> bool {
295    match typing.value(id) {
296        Value::Noise(_) => true,
297        Value::Cast(Cast::Sample, of) => typing.ty(*of).held == Held::Form(Var::T),
298        Value::ClosedForm(form) => form.var == Var::T,
299        _ => false,
300    }
301}
302
303/// Every ref one node reads.
304pub(crate) fn read_operands(typing: &Typing, id: NodeId) -> Vec<NodeId> {
305    match typing.value(id) {
306        Value::ClosedForm(form) => crate::refs::nodes_in(&form.body),
307        _ if typing.ty(id).is_closed_form() => {
308            let mut out = Vec::new();
309            reads_under(typing, id, &mut BTreeSet::new(), &mut out);
310            out.dedup();
311            out
312        }
313        _ => materialized_operands(typing, id),
314    }
315}
316
317/// A closed-form-typed node that holds no written one still reads refs through its operands.
318fn reads_under(typing: &Typing, id: NodeId, seen: &mut BTreeSet<NodeId>, out: &mut Vec<NodeId>) {
319    if !seen.insert(id) {
320        return;
321    }
322    match typing.value(id) {
323        Value::ClosedForm(form) => out.extend(crate::refs::nodes_in(&form.body)),
324        Value::Read { source, .. } => out.push(*source),
325        Value::Cast(_, source) => read_through(typing, *source, seen, out),
326        Value::Op { args, .. } => {
327            for arg in args {
328                read_through(typing, *arg, seen, out);
329            }
330        }
331        Value::Filter {
332            x, cutoff, q, gain, ..
333        } => {
334            for operand in [x, cutoff, q, gain] {
335                read_through(typing, *operand, seen, out);
336            }
337        }
338        Value::Solver { varying, .. } => {
339            for (_, arg) in varying {
340                read_through(typing, *arg, seen, out);
341            }
342        }
343        Value::SelfAt { .. } | Value::Noise(_) => {}
344    }
345}
346
347/// A written form under an operand is the term read, whatever transform sits between.
348fn read_through(typing: &Typing, id: NodeId, seen: &mut BTreeSet<NodeId>, out: &mut Vec<NodeId>) {
349    let Value::ClosedForm(_) = typing.value(id) else {
350        return reads_under(typing, id, seen, out);
351    };
352    if seen.insert(id) {
353        out.push(id);
354    }
355}