Skip to main content

sva_engine/render/
terms.rs

1// Concern: a stream's note sum, one term per handle, each term that ended kept only as its identity | Non-concern: when a value ends (table/), rebuilding nodes | IO: (Expr) -> Handle; (ended) -> ()
2
3use sva_ast::{Arg, BinOp, ByteSpan, Expr, Literal};
4use sva_formula::{Hash, NodeId};
5
6use crate::instantiate::Instances;
7use crate::typing::{SumSlot, Typing, Value};
8
9/// The node a stream defines as the sum of its terms, for its expression to read as `@notes`.
10pub const NOTES: &str = "notes";
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct Handle(pub u32);
14
15#[derive(Clone)]
16struct Term {
17    handle: Handle,
18    expr: Expr,
19    /// False once removed: it plays on, cut there, under no handle.
20    addressed: bool,
21    leaf: Option<NodeId>,
22    ends: Option<NodeId>,
23}
24
25#[derive(Clone)]
26enum Slot {
27    Live(Term),
28    Retired(Hash),
29}
30
31#[derive(Clone, Default)]
32pub(super) struct Terms {
33    next: u32,
34    slots: Vec<Slot>,
35}
36
37impl Terms {
38    fn live(&self) -> impl Iterator<Item = &Term> {
39        self.slots.iter().filter_map(|s| match s {
40            Slot::Live(t) => Some(t),
41            Slot::Retired(_) => None,
42        })
43    }
44
45    fn live_mut(&mut self) -> impl Iterator<Item = &mut Term> {
46        self.slots.iter_mut().filter_map(|s| match s {
47            Slot::Live(t) => Some(t),
48            Slot::Retired(_) => None,
49        })
50    }
51
52    pub(super) fn is_empty(&self) -> bool {
53        self.live().next().is_none()
54    }
55
56    pub(super) fn exprs(&self) -> impl Iterator<Item = &Expr> {
57        self.live().map(|t| &t.expr)
58    }
59
60    pub(super) fn added(&self, expr: Expr) -> (Terms, Handle) {
61        let mut next = self.clone();
62        let handle = Handle(next.next);
63        next.next += 1;
64        next.slots.push(Slot::Live(Term {
65            handle,
66            expr,
67            addressed: true,
68            leaf: None,
69            ends: None,
70        }));
71        (next, handle)
72    }
73
74    /// `None` where the stream no longer holds `handle`.
75    pub(super) fn replaced(&self, handle: Handle, expr: Expr) -> Option<Terms> {
76        let mut next = self.clone();
77        let term = next
78            .live_mut()
79            .find(|t| t.addressed && t.handle == handle)?;
80        (term.expr, term.leaf, term.ends) = (expr, None, None);
81        Some(next)
82    }
83
84    /// The term cut at `at` seconds on the sum's own clock.
85    pub(super) fn removed(&self, handle: Handle, at: f64) -> Option<Terms> {
86        let mut next = self.clone();
87        let term = next
88            .live_mut()
89            .find(|t| t.addressed && t.handle == handle)?;
90        let never = Expr::Bin(
91            BinOp::Sub,
92            Box::new(Expr::Lit(Literal::Num(0.0))),
93            Box::new(Expr::Var("inf".to_string())),
94        );
95        term.expr = Expr::Call {
96            name: "crop".to_string(),
97            args: vec![
98                Arg::Pos(term.expr.clone()),
99                Arg::Pos(never),
100                Arg::Pos(Expr::Lit(Literal::Num(at))),
101            ],
102            span: ByteSpan { start: 0, end: 0 },
103        };
104        (term.addressed, term.leaf, term.ends) = (false, None, None);
105        Some(next)
106    }
107
108    pub(super) fn sum(&self) -> Expr {
109        self.exprs()
110            .cloned()
111            .reduce(|sum, t| Expr::Bin(BinOp::Add, Box::new(sum), Box::new(t)))
112            .unwrap_or(Expr::Lit(Literal::Num(0.0)))
113    }
114
115    /// Each term's own node down the lowered sum's spine, none where the lowering merged it;
116    /// where each has one, `notes` is named by its slots.
117    pub(super) fn typed(&mut self, inst: &Instances, tys: &mut Typing) {
118        let Some(notes) = inst.instance_of(NOTES).ok().and_then(|path| tys.id(&path)) else {
119            return;
120        };
121        let mut at = match tys.value(notes) {
122            Value::Read {
123                source,
124                at: crate::typing::When::At(time),
125                ..
126            } if *time == crate::time::Affine::NOW => Some(*source),
127            _ => Some(notes),
128        };
129        let count = self.live().count();
130        let mut leaves = vec![None; count];
131        for k in (0..count).rev() {
132            let Some(id) = at else {
133                break;
134            };
135            match (k, tys.value(id)) {
136                (0, _) => (leaves[0], at) = (Some(id), None),
137                (_, Value::Op { name, args }) if name == "+" && args.len() == 2 => {
138                    (leaves[k], at) = (Some(args[1]), Some(args[0]));
139                }
140                _ => at = None,
141            }
142        }
143        for (term, leaf) in self.live_mut().zip(leaves) {
144            term.leaf = leaf;
145            term.ends = leaf.map(|id| match tys.value(id) {
146                Value::Read { source, .. } => *source,
147                _ => id,
148            });
149        }
150        let slots = self
151            .slots
152            .iter()
153            .map(|s| match s {
154                Slot::Live(t) => t.leaf.map(SumSlot::Node),
155                Slot::Retired(h) => Some(SumSlot::Retired(*h)),
156            })
157            .collect::<Option<Vec<_>>>();
158        if let Some(slots) = slots {
159            tys.name_sum(notes, slots);
160        }
161    }
162
163    /// Retires every term whose node has ended, bar the last where all have, so what reads
164    /// `notes` keeps its shape and its identity; one with no node of its own just goes. True
165    /// where any went.
166    pub(super) fn prune(
167        &mut self,
168        ended: &dyn Fn(NodeId) -> bool,
169        named: &dyn Fn(NodeId) -> Option<Hash>,
170    ) -> bool {
171        let live = self.live().count();
172        let gone = |t: &Term| t.ends.is_some_and(ended);
173        let keep = match self.live().all(gone) {
174            true => self.live().last().map(|t| t.handle),
175            false => None,
176        };
177        self.slots = std::mem::take(&mut self.slots)
178            .into_iter()
179            .filter_map(|slot| match slot {
180                Slot::Live(t) if gone(&t) && Some(t.handle) != keep => {
181                    t.leaf.and_then(named).map(Slot::Retired)
182                }
183                other => Some(other),
184            })
185            .collect();
186        self.live().count() != live
187    }
188}