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