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