Skip to main content

sva_engine/render/
terms.rs

1// Concern: a stream's note sum, one node per term, each retired term kept as its identity | Non-concern: when a term's support ends (stream.rs), rebuilding nodes | IO: (Expr) -> Handle; (gone) -> ()
2
3use std::sync::atomic::{AtomicU32, Ordering};
4
5use sva_ast::{Address, Arg, BinOp, ByteSpan, Expr, Literal};
6use sva_formula::Hash;
7
8use crate::typing::{SumSlot, Typing};
9
10/// The node a stream defines as the sum of its terms, for its expression to read as `@notes`.
11pub const NOTES: &str = "notes";
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct Handle(pub u32);
15
16impl Handle {
17    pub(super) fn node(self) -> String {
18        format!("{NOTES}#{}", self.0)
19    }
20}
21
22/// Every stream draws from one count, so a handle names one term of one stream.
23static HANDLES: AtomicU32 = AtomicU32::new(0);
24
25#[derive(Clone)]
26struct Term {
27    handle: Handle,
28    expr: Expr,
29    /// False once removed: it plays on, cut there, under no handle.
30    addressed: bool,
31    landed: i64,
32}
33
34impl Term {
35    fn holds(&self, handle: Handle) -> bool {
36        self.addressed && self.handle == handle
37    }
38}
39
40#[derive(Clone)]
41enum Slot {
42    Live(Term),
43    Retired(Hash),
44}
45
46#[derive(Clone, Default)]
47pub(super) struct Terms {
48    slots: Vec<Slot>,
49}
50
51impl Terms {
52    fn live(&self) -> impl Iterator<Item = &Term> {
53        self.slots.iter().filter_map(|s| match s {
54            Slot::Live(t) => Some(t),
55            Slot::Retired(_) => None,
56        })
57    }
58
59    fn live_mut(&mut self) -> impl Iterator<Item = &mut Term> {
60        self.slots.iter_mut().filter_map(|s| match s {
61            Slot::Live(t) => Some(t),
62            Slot::Retired(_) => None,
63        })
64    }
65
66    pub(super) fn count(&self) -> usize {
67        self.live().count()
68    }
69
70    pub(super) fn is_empty(&self) -> bool {
71        self.live().next().is_none()
72    }
73
74    pub(super) fn exprs(&self) -> impl Iterator<Item = &Expr> {
75        self.live().map(|t| &t.expr)
76    }
77
78    pub(super) fn nodes(&self) -> impl Iterator<Item = (String, Expr)> {
79        self.live().map(|t| (t.handle.node(), t.expr.clone()))
80    }
81
82    pub(super) fn handles(&self) -> impl Iterator<Item = Handle> {
83        self.live().map(|t| t.handle)
84    }
85
86    pub(super) fn added(&self, expr: Expr) -> (Terms, Handle) {
87        let mut next = self.clone();
88        let handle = Handle(HANDLES.fetch_add(1, Ordering::Relaxed));
89        next.slots.push(Slot::Live(Term {
90            handle,
91            expr,
92            addressed: true,
93            landed: 0,
94        }));
95        (next, handle)
96    }
97
98    fn held(&mut self, handle: Handle) -> Option<&mut Term> {
99        self.live_mut().find(|t| t.holds(handle))
100    }
101
102    pub(super) fn landed(&self, handle: Handle) -> Option<i64> {
103        Some(self.live().find(|t| t.holds(handle))?.landed)
104    }
105
106    pub(super) fn land(&mut self, handle: Handle, at: i64) {
107        if let Some(term) = self.held(handle) {
108            term.landed = at;
109        }
110    }
111
112    /// `None` where the stream no longer holds `handle`; `expr` of the sample its add landed at.
113    pub(super) fn replaced(&self, handle: Handle, expr: impl FnOnce(i64) -> Expr) -> Option<Terms> {
114        let mut next = self.clone();
115        let term = next.held(handle)?;
116        term.expr = expr(term.landed);
117        Some(next)
118    }
119
120    /// The term cropped at `at` seconds on the sum's own clock.
121    pub(super) fn removed(&self, handle: Handle, at: f64) -> Option<Terms> {
122        let mut next = self.clone();
123        let term = next.held(handle)?;
124        let never = Expr::Bin(
125            BinOp::Sub,
126            Box::new(Expr::Lit(Literal::Num(0.0))),
127            Box::new(Expr::Var("inf".to_string())),
128        );
129        term.expr = Expr::Call {
130            name: "crop".to_string(),
131            args: vec![
132                Arg::Pos(term.expr.clone()),
133                Arg::Pos(never),
134                Arg::Pos(Expr::Lit(Literal::Num(at))),
135            ],
136            span: SPAN,
137        };
138        term.addressed = false;
139        Some(next)
140    }
141
142    pub(super) fn sum(&self) -> Expr {
143        self.live()
144            .map(|t| Expr::Ref {
145                path: t.handle.node(),
146                arg: Box::new(Expr::Var("t".to_string())),
147                binds: Vec::new(),
148                address: Address::Time,
149                span: SPAN,
150            })
151            .reduce(|sum, t| Expr::Bin(BinOp::Add, Box::new(sum), Box::new(t)))
152            .unwrap_or(Expr::Lit(Literal::Num(0.0)))
153    }
154
155    /// `notes` named by its slots, a retired term by the identity it had.
156    pub(super) fn name(&self, tys: &mut Typing) {
157        let Some(notes) = tys.id(NOTES) else {
158            return;
159        };
160        let slots = self.slots.iter().map(|s| match s {
161            Slot::Live(t) => tys.id(&t.handle.node()).map(SumSlot::Node),
162            Slot::Retired(h) => Some(SumSlot::Retired(*h)),
163        });
164        if let Some(slots) = slots.collect::<Option<Vec<_>>>() {
165            tys.name_sum(notes, slots);
166        }
167    }
168
169    /// Retires every term `gone` names, bar the last where all are, so what reads `notes`
170    /// keeps its shape, each as the identity `named` gives it. True where any went.
171    pub(super) fn prune(
172        &mut self,
173        gone: &dyn Fn(Handle) -> bool,
174        named: &dyn Fn(Handle) -> Option<Hash>,
175    ) -> bool {
176        let live = self.live().count();
177        let keep = match self.live().all(|t| gone(t.handle)) {
178            true => self.live().last().map(|t| t.handle),
179            false => None,
180        };
181        self.slots = std::mem::take(&mut self.slots)
182            .into_iter()
183            .filter_map(|slot| match slot {
184                Slot::Live(t) if gone(t.handle) && Some(t.handle) != keep => {
185                    named(t.handle).map(Slot::Retired)
186                }
187                other => Some(other),
188            })
189            .collect();
190        self.live().count() != live
191    }
192}
193
194const SPAN: ByteSpan = ByteSpan { start: 0, end: 0 };
195
196/// `expr` with its sample 0 at sample `at` of the stream: every `t` in it read `at` earlier.
197pub(super) fn placed(expr: &Expr, at: i64) -> Expr {
198    let moved = |e: &Expr| Box::new(placed(e, at));
199    match expr {
200        Expr::Var(name) if name == "t" && at != 0 => Expr::Bin(
201            BinOp::Sub,
202            Box::new(expr.clone()),
203            Box::new(Expr::Lit(Literal::Samples(at as f64))),
204        ),
205        Expr::Lit(_) | Expr::Var(_) => expr.clone(),
206        Expr::Bin(op, l, r) => Expr::Bin(*op, moved(l), moved(r)),
207        Expr::Call { name, args, span } => Expr::Call {
208            name: name.clone(),
209            args: args
210                .iter()
211                .map(|arg| match arg {
212                    Arg::Pos(e) => Arg::Pos(placed(e, at)),
213                    Arg::Named(n, e) => Arg::Named(n.clone(), placed(e, at)),
214                })
215                .collect(),
216            span: *span,
217        },
218        Expr::Ref {
219            path,
220            arg,
221            binds,
222            address,
223            span,
224        } => Expr::Ref {
225            path: path.clone(),
226            arg: moved(arg),
227            binds: binds
228                .iter()
229                .map(|(n, e)| (n.clone(), placed(e, at)))
230                .collect(),
231            address: *address,
232            span: *span,
233        },
234        Expr::SelfRef { arg, address, span } => Expr::SelfRef {
235            arg: moved(arg),
236            address: *address,
237            span: *span,
238        },
239        Expr::Indexed { name, arg, span } => Expr::Indexed {
240            name: name.clone(),
241            arg: moved(arg),
242            span: *span,
243        },
244    }
245}