Skip to main content

sva_engine/render/
stream.rs

1// Concern: opens a target as a stream over memory, editing it and its terms live | Non-concern: pulling its blocks, what an edit carries on | IO: (Graph, target) -> Stream; (Change) -> Changed
2
3use std::cell::RefCell;
4use std::collections::{BTreeMap, BTreeSet};
5use std::sync::Arc;
6
7use sva_ast::{Expr, Graph};
8use sva_formula::{Hash, NodeId};
9use sva_samples::{Buffer, Extent};
10
11use super::drive::Driver;
12use super::drive::Work;
13use super::end::{Ending, Fading, Heard, under};
14use super::terms::{Handle, NOTES, Terms, cut, placed};
15use super::value_graph::support::Supports;
16use super::value_graph::{Past, ValueGraph};
17use super::world::{Plan, Root, STREAMED, Walked, Wanted, World};
18use super::{Ends, RenderConfig, range_over};
19use crate::cache::{
20    Backend, CacheStats, Call, Counters, Kind, Memory, Phase, Recording, Stored, Tier,
21};
22use crate::error::{Diagnostic, EngineError, Located};
23use crate::recent::Recent;
24
25#[cfg(test)]
26mod rebuilt;
27
28/// The lookups, and the names started silent, a stream keeps of all it made.
29pub const LATEST: usize = 256;
30
31#[derive(Clone, Debug, PartialEq)]
32pub struct StreamConfig {
33    pub block: usize,
34    /// The target's own at open where `None`; a mono one plays in each, a wider is refused.
35    pub channels: Option<usize>,
36    pub render: RenderConfig,
37}
38
39/// A target rendered block by block off one value graph, reading `@notes`, the sum of the terms
40/// added. A change builds only what it changed or newly reads; the rest stays as it stood.
41pub struct Stream {
42    config: StreamConfig,
43    world: World,
44    driver: Driver,
45    expr: Expr,
46    terms: Terms,
47    width: usize,
48    /// Each sounding term as heard at the root, through `gain` from the note sum.
49    heard: BTreeMap<Handle, Heard>,
50    gain: Gained,
51    /// Each term retired before its support ended.
52    fading: Vec<Fading>,
53    faded: f64,
54    /// The first sounding term's end, which `heard` evicts.
55    ending: Option<Option<i64>>,
56    /// The sample the stream's root is treated as silent from.
57    treated_as_silent_from_sample: Option<i64>,
58    generation: u64,
59    live: bool,
60    dropped: Recent<String>,
61    late: usize,
62    built: Built,
63    demands: usize,
64    call: Call,
65}
66
67#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
68pub struct Counts {
69    pub dropped: usize,
70    /// Edits that landed past where issued.
71    pub late: usize,
72    pub terms: usize,
73    /// The latest change's, or the open's.
74    pub built: Built,
75    /// The reads that worked out what the root asks of the note sum.
76    pub demands: usize,
77    pub tier: Counters,
78}
79
80/// What one change did anew, not what it carried over.
81#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
82pub struct Built {
83    /// Texts it took in: its expression and each node it newly reads.
84    pub parsed: usize,
85    pub instances: usize,
86    pub visited: usize,
87    pub typed: usize,
88    pub values: usize,
89    /// Values it made taking an old one's state.
90    pub copied: usize,
91    pub lookups: usize,
92}
93
94impl Stream {
95    pub async fn open<B: Backend>(
96        graph: &Graph,
97        target: &Expr,
98        config: StreamConfig,
99        tier: &Tier<B>,
100    ) -> Result<Stream, EngineError> {
101        let call = tier.call(Kind::Stream, STREAMED, Phase::Typing);
102        Stream::open_called(graph, target, config, tier, call).await
103    }
104
105    /// `open` reporting as `call`, one its caller began: every read reports under it.
106    pub async fn open_called<B: Backend>(
107        graph: &Graph,
108        target: &Expr,
109        config: StreamConfig,
110        tier: &Tier<B>,
111        call: Call,
112    ) -> Result<Stream, EngineError> {
113        call.phase(Phase::Typing);
114        let render = blocked(&config)?.render.clone();
115        if graph.defines(STREAMED) {
116            return Err(refusal(format!(
117                "this composition already has a node named `{STREAMED}`"
118            )));
119        }
120        let world = World::over(graph, render.rate, !graph.defines(NOTES));
121        let memory = tier.memory().clone();
122        let recording = Recording::over(&memory).latest(LATEST);
123        let value_graph = ValueGraph::new(&render.profile);
124        let memo = (memory, recording);
125        let driver = Driver::new(value_graph, Extent::new(0, 0), config.block, &render, memo);
126        let mut stream = Stream {
127            world,
128            driver,
129            expr: target.clone(),
130            terms: Terms::default(),
131            width: 0,
132            heard: BTreeMap::new(),
133            gain: Gained::default(),
134            fading: Vec::new(),
135            faded: 0.0,
136            ending: None,
137            treated_as_silent_from_sample: None,
138            generation: 0,
139            live: false,
140            dropped: Recent::keeping(LATEST),
141            late: 0,
142            built: Built::default(),
143            demands: 0,
144            config,
145            call,
146        };
147        let opening = Prospect {
148            target: target.clone(),
149            terms: Terms::default(),
150            term: None,
151            from: None,
152            answer: Changed::Edited,
153            landing: None,
154            parsed: 1,
155        };
156        let mut local = Local::default();
157        let round = tier.begin();
158        loop {
159            match stream.attempt(&opening, &mut local, (tier.memory(), round))? {
160                Attempt::Landed(_) => break,
161                Attempt::Asks(keys) => {
162                    stream.call.phase(Phase::LookingUp);
163                    local.look(keys, tier, round).await
164                }
165                Attempt::Reads(wants) => {
166                    stream.call.phase(Phase::Fetching);
167                    local.read(wants, tier).await
168                }
169                Attempt::Moved => unreachable!("nothing plays a stream before it opens"),
170            }
171        }
172        let mut needs = stream.needs();
173        while !needs.is_empty() {
174            let fetched = tier.fetch(&needs).await;
175            for (key, parts) in fetched.handed {
176                stream.driver.value_graph.took(key, &parts);
177            }
178            needs = fetched.left;
179        }
180        stream.call.rest();
181        stream.delivered();
182        Ok(stream)
183    }
184
185    /// Where it stands, told to its call's subscriber; its total is unknown while its end is.
186    fn delivered(&self) {
187        let driver = &self.driver;
188        let last = driver.end().unwrap_or(driver.last());
189        let total = (last != i64::MAX).then(|| (last - driver.start) as u64);
190        let every = u64::from(self.config.render.rate / 10);
191        let samples = (driver.at - driver.start) as u64;
192        self.call.delivered((samples, total, every), || {
193            (driver.work.computed_samples, driver.recording.tally())
194        });
195    }
196
197    pub fn graph(&self) -> &Graph {
198        &self.world.graph
199    }
200
201    fn prospect(&self, change: Change) -> Result<Prospect, Changed> {
202        let mut landing = None;
203        let prospect = |terms, term: Option<(Handle, Expr)>, from: Option<Graph>, answer| {
204            let roots = |(handle, term): &(Handle, Expr)| sva_ast::reads_of(&handle.node(), term);
205            Prospect {
206                target: self.expr.clone(),
207                terms,
208                from: from.map(|graph| (graph, term.as_ref().map(roots).unwrap_or_default())),
209                term,
210                answer,
211                landing: None,
212                parsed: 1,
213            }
214        };
215        Ok(match change {
216            Change::Target(graph, target) => {
217                let roots = sva_ast::reads_of(STREAMED, &target);
218                Prospect {
219                    target,
220                    from: Some((graph, roots)),
221                    ..prospect(self.terms.clone(), None, None, Changed::Edited)
222                }
223            }
224            Change::Add(graph, term, at) => {
225                let term = match at {
226                    Placed::Written => term,
227                    Placed::Landing => placed(&term, *landing.insert(self.driver.at)),
228                };
229                let (terms, handle) = self.terms.added();
230                Prospect {
231                    landing,
232                    ..prospect(
233                        terms,
234                        Some((handle, term)),
235                        Some(graph),
236                        Changed::Added(handle),
237                    )
238                }
239            }
240            Change::Replace(handle, graph, term, at) => {
241                let landed = self.terms.landed(handle).ok_or(Changed::Held(false))?;
242                let term = match at {
243                    Placed::Written => term,
244                    Placed::Landing => placed(&term, landed),
245                };
246                let terms = self.terms.clone();
247                prospect(
248                    terms,
249                    Some((handle, term)),
250                    Some(graph),
251                    Changed::Held(true),
252                )
253            }
254            Change::Remove(handle) => {
255                let at = self.driver.at;
256                let terms = self.terms.removed(handle).ok_or(Changed::Held(false))?;
257                let held = self.world.graph.expr(&handle.node());
258                let term = cut(held.expect("a held term's node"), at);
259                Prospect {
260                    parsed: 0,
261                    ..prospect(terms, Some((handle, term)), None, Changed::Held(true))
262                }
263            }
264        })
265    }
266
267    /// One try at `prospect`: planned, typed and built beside what plays, then held, or undone
268    /// where memory must answer first.
269    fn attempt(
270        &mut self,
271        prospect: &Prospect,
272        local: &mut Local,
273        (memory, round): (&Memory, u64),
274    ) -> Result<Attempt, EngineError> {
275        if prospect.landing.is_some_and(|at| at != self.driver.at) {
276            return Ok(Attempt::Moved);
277        }
278        let wanted = Wanted {
279            root: Root::Streamed(&prospect.target),
280            terms: &prospect.terms,
281            term: prospect.term.as_ref().map(|(h, e)| (*h, e)),
282            from: prospect.from.as_ref().map(|(g, roots)| (g, roots.clone())),
283            whole: false,
284        };
285        let seen = &mut self.driver.recording;
286        let mut found = |node: &str, key: Hash| memory.answered((key, round), (node, &mut *seen));
287        let walked = self.world.plan(&wanted, &self.config.render, &mut found)?;
288        let mut plan = match walked {
289            Walked::Asks(keys) => return Ok(Attempt::Asks(keys)),
290            Walked::Planned(plan) => plan,
291        };
292        let built = self.built(&mut plan);
293        let (root, range, treated_as_silent_from_sample) = match built {
294            Ok(held) => held,
295            Err(e) => {
296                let freed = self.world.abort();
297                self.driver.value_graph.abort(&freed);
298                return Err(e);
299            }
300        };
301        let from = self.driver.at.max(range.start);
302        let future = Extent::new(from, self.last(range.end).max(from));
303        let value_graph = &mut self.driver.value_graph;
304        let short = value_graph.short((root, future, Past::Stored));
305        let opened = short
306            .into_iter()
307            .try_for_each(|at| value_graph.read_on(&self.world.typing, at, &BTreeMap::new()));
308        if let Err(e) = opened {
309            let freed = self.world.abort();
310            self.driver.value_graph.abort(&freed);
311            return Err(e);
312        }
313        let asked_range = ahead(from, self.config.render.rate);
314        let wants = self.driver.value_graph.needs_made(root, asked_range);
315        let wants: Vec<(Hash, Extent)> = wants
316            .into_iter()
317            .filter(|(key, over)| !local.holds(*key, *over))
318            .collect();
319        if !wants.is_empty() {
320            let freed = self.world.abort();
321            self.driver.value_graph.abort(&freed);
322            return Ok(Attempt::Reads(wants));
323        }
324        self.treated_as_silent_from_sample = treated_as_silent_from_sample;
325        Ok(Attempt::Landed(self.land(
326            prospect,
327            (plan, root, range),
328            local,
329        )))
330    }
331
332    /// The plan typed and built: the root's value, its range, and the sample it is treated as
333    /// silent from.
334    fn built(&mut self, plan: &mut Plan) -> Result<(usize, Extent, Option<i64>), EngineError> {
335        let typing = &mut self.world.typing;
336        let id = typing
337            .id(STREAMED)
338            .ok_or_else(|| EngineError::UnknownNode(STREAMED.to_string()))?;
339        let hits: BTreeMap<NodeId, Arc<Stored>> = plan
340            .stored
341            .iter()
342            .filter_map(|(path, stored)| Some((typing.id(path)?, Arc::clone(stored))))
343            .collect();
344        let value_graph = &mut self.driver.value_graph;
345        let root = value_graph.grow(typing, id, &hits)?;
346        let plays = value_graph.values[root].width;
347        let mut render = self.config.render.clone();
348        match self.width {
349            0 if self.config.channels == Some(0) => {
350                return Err(refusal("a stream of no channels".to_string()));
351            }
352            0 => widens(plays, self.config.channels.unwrap_or(plays))?,
353            width => {
354                widens(plays, width)?;
355                render.range.start = Some(self.driver.start);
356            }
357        }
358        let supports = Supports::over(typing, Some(&value_graph.supports));
359        let ending = Ending::new(typing, &render.profile, &supports);
360        let end = match (render.range.end, self.live) {
361            (None, false) => ending.of(id),
362            _ => ending.exact(id),
363        };
364        let range = range_over((&render, STREAMED), end.support, Ends::Pulled)?;
365        Ok((root, range, end.treated_as_silent_from_sample))
366    }
367
368    /// The change held, each value it made carrying on what it continues.
369    fn land(
370        &mut self,
371        prospect: &Prospect,
372        (mut plan, root, range): (Plan, usize, Extent),
373        local: &mut Local,
374    ) -> Changed {
375        let freed = self.world.commit(std::mem::take(&mut plan.found));
376        let now = self.driver.at;
377        let carried = self
378            .driver
379            .value_graph
380            .settled(root, &freed, (now, self.live));
381        self.driver
382            .value_graph
383            .offers(&self.world.typing, range, None);
384        for (key, parts) in &local.fetched {
385            self.driver.value_graph.took(*key, parts);
386        }
387        for at in &carried.silent {
388            self.dropped
389                .push(self.driver.value_graph.values[*at].name.clone());
390        }
391        match self.width {
392            0 => {
393                let plays = self.driver.value_graph.values[root].width;
394                self.width = self.config.channels.unwrap_or(plays);
395                (self.driver.start, self.driver.at) = (range.start, range.start);
396            }
397            _ => self.late += usize::from(self.driver.at > local.issued),
398        }
399        let last = self.last(range.end);
400        self.driver.bound(last);
401        self.expr = prospect.target.clone();
402        self.terms = prospect.terms.clone();
403        if let Changed::Added(handle) = prospect.answer {
404            self.terms.land(handle, self.driver.at);
405        }
406        self.hear();
407        self.built = Built {
408            parsed: prospect.parsed + plan.adopted,
409            instances: plan.named,
410            visited: plan.visited,
411            typed: self.world.typing.lowered().len(),
412            values: self.driver.value_graph.built,
413            copied: carried.taken,
414            lookups: local.lookups,
415        };
416        self.generation += 1;
417        self.retire_terms_below_silence_threshold();
418        prospect.answer
419    }
420
421    /// A term is its own sound, judged at the root through the note sum's gain to it, bounded
422    /// again only where the key of what it is a function of moved.
423    fn hear(&mut self) {
424        let typing = &self.world.typing;
425        let supports = Supports::over(typing, Some(&self.driver.value_graph.supports));
426        let ending = Ending::new(typing, &self.config.render.profile, &supports);
427        let ends = typing.id(STREAMED).zip(typing.id(NOTES));
428        let key = ends.and_then(|(root, notes)| ending.gain_key(root, notes));
429        let gain = match ends {
430            _ if key.is_some() && key == self.gain.of => self.gain.gain,
431            Some((root, notes)) => ending.gain(root, notes),
432            None => None,
433        };
434        let moved = gain != std::mem::replace(&mut self.gain, Gained { gain, of: key }).gain;
435        let lowered: BTreeSet<&str> = typing.lowered().iter().map(String::as_str).collect();
436        for handle in self.terms.handles() {
437            let node = handle.node();
438            if !moved && !lowered.contains(node.as_str()) && self.heard.contains_key(&handle) {
439                continue;
440            }
441            let id = typing.id(&node).expect("a sounding term is typed");
442            self.heard.insert(handle, ending.heard(id, gain));
443        }
444        self.ending = None;
445    }
446
447    fn needs(&self) -> Vec<(Hash, Extent)> {
448        let next = ahead(self.driver.at, self.config.render.rate);
449        self.driver.value_graph.needs(next)
450    }
451
452    /// `n` samples from `at`, `None` past the end. An `at` behind is refused; one ahead skips
453    /// there, computing through the span, or, live, as `go_live` says.
454    pub fn read(&mut self, at: i64, n: usize) -> Result<Option<Buffer>, EngineError> {
455        let now = self.driver.at;
456        if at < now {
457            return Err(refused(
458                "engine.stream_behind",
459                format!("sample {at} is before sample {now}, where the stream stands"),
460                "read from the stream's position or later",
461            ));
462        }
463        if n == 0 {
464            return Err(refused(
465                "engine.empty_read",
466                format!("a read of no samples at sample {at}"),
467                "read one sample or more",
468            ));
469        }
470        match self.live {
471            true if at > now => {
472                for silenced in self.driver.skip(at)? {
473                    self.dropped
474                        .push(self.driver.value_graph.values[silenced].name.clone());
475                }
476            }
477            _ => {
478                let block = self.config.block;
479                while self.driver.at < at {
480                    let step = block.min((at - self.driver.at) as usize);
481                    self.reads_on(step)?;
482                    if !self.driver.pulled(step)? {
483                        return Ok(None);
484                    }
485                    self.retire_terms_below_silence_threshold();
486                }
487            }
488        }
489        self.reads_on(n)?;
490        let block = self.driver.read(n)?;
491        self.retire_terms_below_silence_threshold();
492        self.delivered();
493        Ok(block.map(|b| widened(b, self.width)))
494    }
495
496    /// Each node memory answered that the next `n` samples ask past what it holds reads on from
497    /// its own value, made as the change that made it would have: live, a stateful one already
498    /// sounding then starts silent.
499    fn reads_on(&mut self, n: usize) -> Result<(), EngineError> {
500        let at = self.driver.at;
501        let range = Extent::new(self.driver.start, self.driver.last());
502        let asked_range = Extent::new(at, at.saturating_add(n as i64).min(range.end).max(at));
503        let value_graph = &mut self.driver.value_graph;
504        if asked_range.is_empty() {
505            return Ok(());
506        }
507        for short in value_graph.short((value_graph.root, asked_range, Past::Held)) {
508            value_graph.read_on(&self.world.typing, short, &BTreeMap::new())?;
509            let landed = value_graph.landed(short);
510            let carried = value_graph.settled(value_graph.root, &[], (landed, self.live));
511            for silent in carried.silent {
512                self.dropped.push(value_graph.values[silent].name.clone());
513            }
514            value_graph.offers(&self.world.typing, range, None);
515        }
516        Ok(())
517    }
518
519    /// An edited node with no state, or one a read skips past, starts silent, never computing
520    /// its past, named in `dropped`; a formula reads on exactly.
521    pub fn go_live(&mut self) {
522        self.live = true;
523        let last = self.last(self.driver.last());
524        self.driver.bound(last);
525    }
526
527    fn last(&self, range_end: i64) -> i64 {
528        match self.live {
529            true => self.config.render.range.end.unwrap_or(i64::MAX),
530            false => range_end,
531        }
532    }
533
534    /// The latest nodes a live edit started silent, of `counts().dropped`.
535    pub fn dropped(&self) -> Vec<&str> {
536        self.dropped.iter().map(String::as_str).collect()
537    }
538
539    pub fn counts(&self) -> Counts {
540        Counts {
541            dropped: self.dropped.made(),
542            late: self.late,
543            terms: self.terms.count(),
544            built: self.built,
545            demands: self.demands,
546            tier: self.driver.recording.since(&self.driver.memory),
547        }
548    }
549
550    /// Retires every term silent at the root by now and from the first sample of `notes` the
551    /// root asks, while all it retired early, summed through the gain, stay under the silence
552    /// threshold.
553    fn retire_terms_below_silence_threshold(&mut self) {
554        let now = self.driver.at;
555        let heard = &self.heard;
556        let ending = *self
557            .ending
558            .get_or_insert_with(|| heard.values().map(|h| h.support.end).min());
559        if ending.is_none_or(|end| end > now) {
560            return;
561        }
562        let (value_graph, tys) = (&self.driver.value_graph, &self.world.typing);
563        let last = self.driver.last();
564        let asked = match tys.id(NOTES).and_then(|notes| value_graph.of(notes)) {
565            Some(notes) if now < last => {
566                self.demands += 1;
567                let needs = value_graph.demand(Extent::new(now, last));
568                needs[notes].hold.iter().next().map(|asked| asked.start)
569            }
570            Some(_) => None,
571            None => Some(i64::MIN),
572        };
573        let silence_threshold = self.config.render.profile.silence_threshold_amplitude();
574        let gain = self.gain.gain.unwrap_or(f64::INFINITY);
575        if let Some(from) = asked {
576            // Far under the silence threshold, a bound joins one sum, so the list stays short.
577            let let_go = silence_threshold * 2f64.powi(-30);
578            let mut faded = self.faded;
579            self.fading.retain(|f| match f.from(from) {
580                b if b <= let_go => {
581                    faded = (faded + b) * (1.0 + f64::EPSILON);
582                    false
583                }
584                _ => true,
585            });
586            self.faded = faded;
587        }
588        let mut gone = BTreeSet::new();
589        for (handle, h) in &self.heard {
590            let end = h.support.end;
591            if end > now || asked.is_some_and(|from| end > from) {
592                continue;
593            }
594            let (Some(from), Some(fading)) = (asked, &h.fading) else {
595                gone.insert(*handle);
596                continue;
597            };
598            let own = fading.from(from);
599            if own > 0.0 {
600                let left: f64 = self.fading.iter().map(|f| f.from(from)).sum();
601                let ops = self.fading.len() as f64 + 3.0;
602                let left = (self.faded + left + own) * (1.0 + ops * f64::EPSILON);
603                if !under(gain, left, silence_threshold) {
604                    continue;
605                }
606                self.fading.push(fading.clone());
607            }
608            gone.insert(*handle);
609        }
610        let heard = &self.heard;
611        let support = |handle: Handle| heard.get(&handle).map(|h| h.support);
612        let went = self
613            .terms
614            .retire(&|handle| gone.contains(&handle), &support);
615        if !went.is_empty() {
616            self.generation += 1;
617        }
618        for handle in went {
619            self.heard.remove(&handle);
620            self.ending = None;
621        }
622    }
623
624    /// Every segment of its own clock the stream computed of `node`'s value, in order.
625    pub fn evaluated(&self, node: &str) -> Vec<sva_samples::Extent> {
626        let value_graph = &self.driver.value_graph;
627        self.world
628            .typing
629            .id(node)
630            .and_then(|id| value_graph.of(id))
631            .map_or(Vec::new(), |at| value_graph.values[at].evaluated.clone())
632    }
633
634    pub fn cutting_below_silence_threshold(&self) -> sva_samples::CuttingBelowSilenceThreshold {
635        sva_samples::CuttingBelowSilenceThreshold {
636            silence_threshold_dbfs: self.config.render.profile.silence_threshold_dbfs,
637            treated_as_silent_from_sample: self
638                .treated_as_silent_from_sample
639                .map(|at| (STREAMED.to_string(), at))
640                .into_iter()
641                .collect(),
642        }
643    }
644
645    pub fn landed(&self, handle: Handle) -> Option<i64> {
646        self.terms.landed(handle)
647    }
648
649    pub fn position(&self) -> i64 {
650        self.driver.at
651    }
652
653    pub fn work(&self) -> Work {
654        self.driver.work
655    }
656
657    pub fn stats(&self) -> CacheStats {
658        self.driver.recording.stats(&self.driver.memory)
659    }
660
661    /// The bytes its values hold, samples and state.
662    pub fn held_bytes(&self) -> usize {
663        self.driver.value_graph.bytes()
664    }
665
666    pub fn end(&self) -> Option<i64> {
667        self.driver.end()
668    }
669
670    pub fn width(&self) -> usize {
671        self.width
672    }
673
674    pub fn config(&self) -> &StreamConfig {
675        &self.config
676    }
677}
678
679#[derive(Clone, Copy, Default)]
680struct Gained {
681    gain: Option<f64>,
682    of: Option<Hash>,
683}
684
685/// An edit, each with the graph that reaches it and all the stream plays.
686pub enum Change {
687    Target(Graph, Expr),
688    Add(Graph, Expr, Placed),
689    Replace(Handle, Graph, Expr, Placed),
690    /// A sounding term is cut where the stream stands as the edit is built; what played stays.
691    Remove(Handle),
692}
693
694/// Where a term's sample 0 sits: the stream's own, or the sample its add lands at.
695#[derive(Clone, Copy, Debug, PartialEq, Eq)]
696pub enum Placed {
697    Written,
698    Landing,
699}
700
701/// A replace or remove answers whether the stream still held its handle.
702#[derive(Clone, Copy, Debug, PartialEq, Eq)]
703pub enum Changed {
704    Edited,
705    Added(Handle),
706    Held(bool),
707}
708
709/// What a change wants played, and what it answers once it lands.
710struct Prospect {
711    target: Expr,
712    terms: Terms,
713    term: Option<(Handle, Expr)>,
714    from: Option<(Graph, Vec<String>)>,
715    answer: Changed,
716    landing: Option<i64>,
717    /// Its own expression, where it brought one.
718    parsed: usize,
719}
720
721/// What one try at a change came to.
722enum Attempt {
723    Landed(Changed),
724    Asks(Vec<Hash>),
725    Reads(Vec<(Hash, Extent)>),
726    /// The stream left the sample it was placed at.
727    Moved,
728}
729
730/// What one change has looked up and read, across its tries.
731#[derive(Default)]
732struct Local {
733    fetched: Vec<(Hash, Vec<Arc<Buffer>>)>,
734    asked: Vec<(Hash, Extent)>,
735    unread: BTreeSet<Hash>,
736    lookups: usize,
737    issued: i64,
738}
739
740impl Local {
741    async fn look<B: Backend>(&mut self, keys: Vec<Hash>, tier: &Tier<B>, round: u64) {
742        for key in keys {
743            self.lookups += 1;
744            tier.lookup(key, round).await;
745        }
746    }
747
748    /// What `wants` asks, off memory; one a fetch's reads did not reach is asked again.
749    async fn read<B: Backend>(&mut self, wants: Vec<(Hash, Extent)>, tier: &Tier<B>) {
750        let fetched = tier.fetch(&wants).await;
751        for (key, over) in wants {
752            if fetched.left.contains(&(key, over)) {
753                continue;
754            }
755            self.asked.push((key, over));
756            if !fetched.handed.iter().any(|(held, _)| *held == key) {
757                self.unread.insert(key);
758            }
759        }
760        self.fetched.extend(fetched.handed);
761    }
762
763    /// Whether this change already read `key` over `over`, or could not.
764    fn holds(&self, key: Hash, over: Extent) -> bool {
765        self.unread.contains(&key)
766            || self
767                .asked
768                .iter()
769                .any(|(k, e)| *k == key && e.intersect(over) == over)
770    }
771}
772
773/// `build`'s change, the stream held only while each try runs, between two blocks: an exact
774/// stream pulled once its edit is done plays it where issued. `build` runs again whenever the
775/// stream changed under it.
776pub async fn change<E: From<EngineError>, B: Backend>(
777    stream: &RefCell<Stream>,
778    build: impl FnMut(&Stream) -> Result<Change, E>,
779    tier: &Tier<B>,
780) -> Result<Changed, E> {
781    let target = stream.borrow().call.target().to_string();
782    let call = tier.call(Kind::Edit, &target, Phase::Typing);
783    let changed = changing(stream, build, (tier, &call)).await;
784    if changed.is_ok() {
785        call.end(Phase::Done);
786    }
787    changed
788}
789
790async fn changing<E: From<EngineError>, B: Backend>(
791    stream: &RefCell<Stream>,
792    mut build: impl FnMut(&Stream) -> Result<Change, E>,
793    (tier, call): (&Tier<B>, &Call),
794) -> Result<Changed, E> {
795    let mut local = Local {
796        issued: stream.borrow().driver.at,
797        ..Local::default()
798    };
799    let round = tier.begin();
800    stream.borrow_mut().driver.recording.begin();
801    loop {
802        let change = build(&stream.borrow())?;
803        let prospect = match stream.borrow().prospect(change) {
804            Ok(prospect) => prospect,
805            Err(answer) => return Ok(answer),
806        };
807        let generation = stream.borrow().generation;
808        loop {
809            if stream.borrow().generation != generation {
810                break;
811            }
812            let attempt =
813                stream
814                    .borrow_mut()
815                    .attempt(&prospect, &mut local, (tier.memory(), round))?;
816            match attempt {
817                Attempt::Landed(answer) => return Ok(answer),
818                Attempt::Moved => break,
819                Attempt::Asks(keys) => {
820                    call.phase(Phase::LookingUp);
821                    local.look(keys, tier, round).await
822                }
823                Attempt::Reads(wants) => {
824                    call.phase(Phase::Fetching);
825                    local.read(wants, tier).await
826                }
827            }
828        }
829    }
830}
831
832/// What the next second of `stream` reads off memory, as far as one fetch reaches; a block
833/// reading samples not ready computes them, or, live, starts them silent.
834pub async fn fetch<B: Backend>(stream: &RefCell<Stream>, tier: &Tier<B>) {
835    let needs = stream.borrow().needs();
836    let fetched = tier.fetch(&needs).await;
837    let mut stream = stream.borrow_mut();
838    for (key, parts) in fetched.handed {
839        stream.driver.value_graph.took(key, &parts);
840    }
841}
842
843fn ahead(at: i64, rate: u32) -> Extent {
844    Extent::new(at, at.saturating_add(i64::from(rate)))
845}
846
847fn widens(plays: usize, width: usize) -> Result<(), EngineError> {
848    match plays == width || plays == 1 {
849        true => Ok(()),
850        false => Err(refused(
851            "engine.stream_width",
852            format!("this plays {plays} channel(s), and the stream plays {width}"),
853            "play as many channels as the stream, or one, or open a new stream for it",
854        )),
855    }
856}
857
858fn blocked(config: &StreamConfig) -> Result<&StreamConfig, EngineError> {
859    match config.block {
860        0 => Err(refusal("a block of no samples".to_string())),
861        _ => Ok(config),
862    }
863}
864
865pub(super) fn refusal(what: String) -> EngineError {
866    refused(
867        "engine.no_stream",
868        format!("this target opens no stream: {what}"),
869        "stream an expression over the nodes the composition defines",
870    )
871}
872
873fn refused(code: &str, message: String, help: &str) -> EngineError {
874    EngineError::refused(Diagnostic {
875        code: code.to_string(),
876        message,
877        location: Located::at(STREAMED, None),
878        help: help.to_string(),
879    })
880}
881
882/// A mono block in each of `width` channels; any other as it is.
883fn widened(mut block: Buffer, width: usize) -> Buffer {
884    if block.planes.len() == 1 {
885        let copies = vec![block.planes[0].clone(); width.saturating_sub(1)];
886        block.planes.extend(copies);
887    }
888    block
889}