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.progress(), 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            told: None,
285        };
286        let seen = &mut self.driver.recording;
287        let mut found = |node: &str, key: Hash| memory.answered((key, round), (node, &mut *seen));
288        let walked = self.world.plan(&wanted, &self.config.render, &mut found)?;
289        let mut plan = match walked {
290            Walked::Asks(keys) => return Ok(Attempt::Asks(keys)),
291            Walked::Planned(plan) => plan,
292        };
293        let built = self.built(&mut plan);
294        let (root, range, treated_as_silent_from_sample) = match built {
295            Ok(held) => held,
296            Err(e) => {
297                let freed = self.world.abort();
298                self.driver.value_graph.abort(&freed);
299                return Err(e);
300            }
301        };
302        let from = self.driver.at.max(range.start);
303        let future = Extent::new(from, self.last(range.end).max(from));
304        let value_graph = &mut self.driver.value_graph;
305        let short = value_graph.short((root, future, Past::Stored));
306        let opened = short
307            .into_iter()
308            .try_for_each(|at| value_graph.read_on(&self.world.typing, at, &BTreeMap::new()));
309        if let Err(e) = opened {
310            let freed = self.world.abort();
311            self.driver.value_graph.abort(&freed);
312            return Err(e);
313        }
314        let asked_range = ahead(from, self.config.render.rate);
315        let wants = self.driver.value_graph.needs_made(root, asked_range);
316        let wants: Vec<(Hash, Extent)> = wants
317            .into_iter()
318            .filter(|(key, over)| !local.holds(*key, *over))
319            .collect();
320        if !wants.is_empty() {
321            let freed = self.world.abort();
322            self.driver.value_graph.abort(&freed);
323            return Ok(Attempt::Reads(wants));
324        }
325        self.treated_as_silent_from_sample = treated_as_silent_from_sample;
326        Ok(Attempt::Landed(self.land(
327            prospect,
328            (plan, root, range),
329            local,
330        )))
331    }
332
333    /// The plan typed and built: the root's value, its range, and the sample it is treated as
334    /// silent from.
335    fn built(&mut self, plan: &mut Plan) -> Result<(usize, Extent, Option<i64>), EngineError> {
336        let typing = &mut self.world.typing;
337        let id = typing
338            .id(STREAMED)
339            .ok_or_else(|| EngineError::UnknownNode(STREAMED.to_string()))?;
340        let hits: BTreeMap<NodeId, Arc<Stored>> = plan
341            .stored
342            .iter()
343            .filter_map(|(path, stored)| Some((typing.id(path)?, Arc::clone(stored))))
344            .collect();
345        let value_graph = &mut self.driver.value_graph;
346        let root = value_graph.grow(typing, id, &hits)?;
347        let plays = value_graph.values[root].width;
348        let mut render = self.config.render.clone();
349        match self.width {
350            0 if self.config.channels == Some(0) => {
351                return Err(refusal("a stream of no channels".to_string()));
352            }
353            0 => widens(plays, self.config.channels.unwrap_or(plays))?,
354            width => {
355                widens(plays, width)?;
356                render.range.start = Some(self.driver.start);
357            }
358        }
359        let supports = Supports::over(typing, Some(&value_graph.supports));
360        let ending = Ending::new(typing, &render.profile, &supports);
361        let end = match (render.range.end, self.live) {
362            (None, false) => ending.of(id),
363            _ => ending.exact(id),
364        };
365        let range = range_over((&render, STREAMED), end.support, Ends::Pulled)?;
366        Ok((root, range, end.treated_as_silent_from_sample))
367    }
368
369    /// The change held, each value it made carrying on what it continues.
370    fn land(
371        &mut self,
372        prospect: &Prospect,
373        (mut plan, root, range): (Plan, usize, Extent),
374        local: &mut Local,
375    ) -> Changed {
376        let freed = self.world.commit(std::mem::take(&mut plan.found));
377        let now = self.driver.at;
378        let carried = self
379            .driver
380            .value_graph
381            .settled(root, &freed, (now, self.live));
382        self.driver
383            .value_graph
384            .offers(&self.world.typing, range, None);
385        for (key, parts) in &local.fetched {
386            self.driver.value_graph.took(*key, parts);
387        }
388        for at in &carried.silent {
389            self.dropped
390                .push(self.driver.value_graph.values[*at].name.clone());
391        }
392        match self.width {
393            0 => {
394                let plays = self.driver.value_graph.values[root].width;
395                self.width = self.config.channels.unwrap_or(plays);
396                (self.driver.start, self.driver.at) = (range.start, range.start);
397            }
398            _ => self.late += usize::from(self.driver.at > local.issued),
399        }
400        let last = self.last(range.end);
401        self.driver.bound(last);
402        self.expr = prospect.target.clone();
403        self.terms = prospect.terms.clone();
404        if let Changed::Added(handle) = prospect.answer {
405            self.terms.land(handle, self.driver.at);
406        }
407        self.hear();
408        self.built = Built {
409            parsed: prospect.parsed + plan.adopted,
410            instances: plan.named,
411            visited: plan.visited,
412            typed: self.world.typing.lowered().len(),
413            values: self.driver.value_graph.built,
414            copied: carried.taken,
415            lookups: local.lookups,
416        };
417        self.generation += 1;
418        self.retire_terms_below_silence_threshold();
419        prospect.answer
420    }
421
422    /// A term is its own sound, judged at the root through the note sum's gain to it, bounded
423    /// again only where the key of what it is a function of moved.
424    fn hear(&mut self) {
425        let typing = &self.world.typing;
426        let supports = Supports::over(typing, Some(&self.driver.value_graph.supports));
427        let ending = Ending::new(typing, &self.config.render.profile, &supports);
428        let ends = typing.id(STREAMED).zip(typing.id(NOTES));
429        let key = ends.and_then(|(root, notes)| ending.gain_key(root, notes));
430        let gain = match ends {
431            _ if key.is_some() && key == self.gain.of => self.gain.gain,
432            Some((root, notes)) => ending.gain(root, notes),
433            None => None,
434        };
435        let moved = gain != std::mem::replace(&mut self.gain, Gained { gain, of: key }).gain;
436        let lowered: BTreeSet<&str> = typing.lowered().iter().map(String::as_str).collect();
437        for handle in self.terms.handles() {
438            let node = handle.node();
439            if !moved && !lowered.contains(node.as_str()) && self.heard.contains_key(&handle) {
440                continue;
441            }
442            let id = typing.id(&node).expect("a sounding term is typed");
443            self.heard.insert(handle, ending.heard(id, gain));
444        }
445        self.ending = None;
446    }
447
448    fn needs(&self) -> Vec<(Hash, Extent)> {
449        let next = ahead(self.driver.at, self.config.render.rate);
450        self.driver.value_graph.needs(next)
451    }
452
453    /// `n` samples from `at`, `None` past the end. An `at` behind is refused; one ahead skips
454    /// there, computing through the span, or, live, as `go_live` says.
455    pub fn read(&mut self, at: i64, n: usize) -> Result<Option<Buffer>, EngineError> {
456        let now = self.driver.at;
457        if at < now {
458            return Err(refused(
459                "engine.stream_behind",
460                format!("sample {at} is before sample {now}, where the stream stands"),
461                "read from the stream's position or later",
462            ));
463        }
464        if n == 0 {
465            return Err(refused(
466                "engine.empty_read",
467                format!("a read of no samples at sample {at}"),
468                "read one sample or more",
469            ));
470        }
471        match self.live {
472            true if at > now => {
473                for silenced in self.driver.skip(at)? {
474                    self.dropped
475                        .push(self.driver.value_graph.values[silenced].name.clone());
476                }
477            }
478            _ => {
479                let block = self.config.block;
480                while self.driver.at < at {
481                    let step = block.min((at - self.driver.at) as usize);
482                    self.reads_on(step)?;
483                    let hears = |progress| self.call.worked(progress);
484                    let heard = self.call.subscribed().then_some(&hears as &dyn Fn(_));
485                    if !self.driver.pulled(step, heard)? {
486                        return Ok(None);
487                    }
488                    self.retire_terms_below_silence_threshold();
489                }
490            }
491        }
492        self.reads_on(n)?;
493        let hears = |progress| self.call.worked(progress);
494        let heard = self.call.subscribed().then_some(&hears as &dyn Fn(_));
495        let block = self.driver.read(n, heard)?;
496        self.retire_terms_below_silence_threshold();
497        self.delivered();
498        Ok(block.map(|b| widened(b, self.width)))
499    }
500
501    /// Each node memory answered that the next `n` samples ask past what it holds reads on from
502    /// its own value, made as the change that made it would have: live, a stateful one already
503    /// sounding then starts silent.
504    fn reads_on(&mut self, n: usize) -> Result<(), EngineError> {
505        let at = self.driver.at;
506        let range = Extent::new(self.driver.start, self.driver.last());
507        let asked_range = Extent::new(at, at.saturating_add(n as i64).min(range.end).max(at));
508        let value_graph = &mut self.driver.value_graph;
509        if asked_range.is_empty() {
510            return Ok(());
511        }
512        for short in value_graph.short((value_graph.root, asked_range, Past::Held)) {
513            value_graph.read_on(&self.world.typing, short, &BTreeMap::new())?;
514            let landed = value_graph.landed(short);
515            let carried = value_graph.settled(value_graph.root, &[], (landed, self.live));
516            for silent in carried.silent {
517                self.dropped.push(value_graph.values[silent].name.clone());
518            }
519            value_graph.offers(&self.world.typing, range, None);
520        }
521        Ok(())
522    }
523
524    /// An edited node with no state, or one a read skips past, starts silent, never computing
525    /// its past, named in `dropped`; a formula reads on exactly.
526    pub fn go_live(&mut self) {
527        self.live = true;
528        let last = self.last(self.driver.last());
529        self.driver.bound(last);
530    }
531
532    fn last(&self, range_end: i64) -> i64 {
533        match self.live {
534            true => self.config.render.range.end.unwrap_or(i64::MAX),
535            false => range_end,
536        }
537    }
538
539    /// The latest nodes a live edit started silent, of `counts().dropped`.
540    pub fn dropped(&self) -> Vec<&str> {
541        self.dropped.iter().map(String::as_str).collect()
542    }
543
544    pub fn counts(&self) -> Counts {
545        Counts {
546            dropped: self.dropped.made(),
547            late: self.late,
548            terms: self.terms.count(),
549            built: self.built,
550            demands: self.demands,
551            tier: self.driver.recording.since(&self.driver.memory),
552        }
553    }
554
555    /// Retires every term silent at the root by now and from the first sample of `notes` the
556    /// root asks, while all it retired early, summed through the gain, stay under the silence
557    /// threshold.
558    fn retire_terms_below_silence_threshold(&mut self) {
559        let now = self.driver.at;
560        let heard = &self.heard;
561        let ending = *self
562            .ending
563            .get_or_insert_with(|| heard.values().map(|h| h.support.end).min());
564        if ending.is_none_or(|end| end > now) {
565            return;
566        }
567        let (value_graph, tys) = (&self.driver.value_graph, &self.world.typing);
568        let last = self.driver.last();
569        let asked = match tys.id(NOTES).and_then(|notes| value_graph.of(notes)) {
570            Some(notes) if now < last => {
571                self.demands += 1;
572                let needs = value_graph.demand(Extent::new(now, last));
573                needs[notes].hold.iter().next().map(|asked| asked.start)
574            }
575            Some(_) => None,
576            None => Some(i64::MIN),
577        };
578        let silence_threshold = self.config.render.profile.silence_threshold_amplitude();
579        let gain = self.gain.gain.unwrap_or(f64::INFINITY);
580        if let Some(from) = asked {
581            // Far under the silence threshold, a bound joins one sum, so the list stays short.
582            let let_go = silence_threshold * 2f64.powi(-30);
583            let mut faded = self.faded;
584            self.fading.retain(|f| match f.from(from) {
585                b if b <= let_go => {
586                    faded = (faded + b) * (1.0 + f64::EPSILON);
587                    false
588                }
589                _ => true,
590            });
591            self.faded = faded;
592        }
593        let mut gone = BTreeSet::new();
594        for (handle, h) in &self.heard {
595            let end = h.support.end;
596            if end > now || asked.is_some_and(|from| end > from) {
597                continue;
598            }
599            let (Some(from), Some(fading)) = (asked, &h.fading) else {
600                gone.insert(*handle);
601                continue;
602            };
603            let own = fading.from(from);
604            if own > 0.0 {
605                let left: f64 = self.fading.iter().map(|f| f.from(from)).sum();
606                let ops = self.fading.len() as f64 + 3.0;
607                let left = (self.faded + left + own) * (1.0 + ops * f64::EPSILON);
608                if !under(gain, left, silence_threshold) {
609                    continue;
610                }
611                self.fading.push(fading.clone());
612            }
613            gone.insert(*handle);
614        }
615        let heard = &self.heard;
616        let support = |handle: Handle| heard.get(&handle).map(|h| h.support);
617        let went = self
618            .terms
619            .retire(&|handle| gone.contains(&handle), &support);
620        if !went.is_empty() {
621            self.generation += 1;
622        }
623        for handle in went {
624            self.heard.remove(&handle);
625            self.ending = None;
626        }
627    }
628
629    /// Every segment of its own clock the stream computed of `node`'s value, in order.
630    pub fn evaluated(&self, node: &str) -> Vec<sva_samples::Extent> {
631        let value_graph = &self.driver.value_graph;
632        self.world
633            .typing
634            .id(node)
635            .and_then(|id| value_graph.of(id))
636            .map_or(Vec::new(), |at| value_graph.values[at].evaluated.clone())
637    }
638
639    pub fn cutting_below_silence_threshold(&self) -> sva_samples::CuttingBelowSilenceThreshold {
640        sva_samples::CuttingBelowSilenceThreshold {
641            silence_threshold_dbfs: self.config.render.profile.silence_threshold_dbfs,
642            treated_as_silent_from_sample: self
643                .treated_as_silent_from_sample
644                .map(|at| (STREAMED.to_string(), at))
645                .into_iter()
646                .collect(),
647        }
648    }
649
650    pub fn landed(&self, handle: Handle) -> Option<i64> {
651        self.terms.landed(handle)
652    }
653
654    pub fn position(&self) -> i64 {
655        self.driver.at
656    }
657
658    pub fn work(&self) -> Work {
659        self.driver.work()
660    }
661
662    pub fn stats(&self) -> CacheStats {
663        self.driver.recording.stats(&self.driver.memory)
664    }
665
666    /// The bytes its values hold, samples and state.
667    pub fn held_bytes(&self) -> usize {
668        self.driver.value_graph.bytes()
669    }
670
671    pub fn end(&self) -> Option<i64> {
672        self.driver.end()
673    }
674
675    pub fn width(&self) -> usize {
676        self.width
677    }
678
679    pub fn config(&self) -> &StreamConfig {
680        &self.config
681    }
682}
683
684#[derive(Clone, Copy, Default)]
685struct Gained {
686    gain: Option<f64>,
687    of: Option<Hash>,
688}
689
690/// An edit, each with the graph that reaches it and all the stream plays.
691pub enum Change {
692    Target(Graph, Expr),
693    Add(Graph, Expr, Placed),
694    Replace(Handle, Graph, Expr, Placed),
695    /// A sounding term is cut where the stream stands as the edit is built; what played stays.
696    Remove(Handle),
697}
698
699/// Where a term's sample 0 sits: the stream's own, or the sample its add lands at.
700#[derive(Clone, Copy, Debug, PartialEq, Eq)]
701pub enum Placed {
702    Written,
703    Landing,
704}
705
706/// A replace or remove answers whether the stream still held its handle.
707#[derive(Clone, Copy, Debug, PartialEq, Eq)]
708pub enum Changed {
709    Edited,
710    Added(Handle),
711    Held(bool),
712}
713
714/// What a change wants played, and what it answers once it lands.
715struct Prospect {
716    target: Expr,
717    terms: Terms,
718    term: Option<(Handle, Expr)>,
719    from: Option<(Graph, Vec<String>)>,
720    answer: Changed,
721    landing: Option<i64>,
722    /// Its own expression, where it brought one.
723    parsed: usize,
724}
725
726/// What one try at a change came to.
727enum Attempt {
728    Landed(Changed),
729    Asks(Vec<Hash>),
730    Reads(Vec<(Hash, Extent)>),
731    /// The stream left the sample it was placed at.
732    Moved,
733}
734
735/// What one change has looked up and read, across its tries.
736#[derive(Default)]
737struct Local {
738    fetched: Vec<(Hash, Vec<Arc<Buffer>>)>,
739    asked: Vec<(Hash, Extent)>,
740    unread: BTreeSet<Hash>,
741    lookups: usize,
742    issued: i64,
743}
744
745impl Local {
746    async fn look<B: Backend>(&mut self, keys: Vec<Hash>, tier: &Tier<B>, round: u64) {
747        for key in keys {
748            self.lookups += 1;
749            tier.lookup(key, round).await;
750        }
751    }
752
753    /// What `wants` asks, off memory; one a fetch's reads did not reach is asked again.
754    async fn read<B: Backend>(&mut self, wants: Vec<(Hash, Extent)>, tier: &Tier<B>) {
755        let fetched = tier.fetch(&wants).await;
756        for (key, over) in wants {
757            if fetched.left.contains(&(key, over)) {
758                continue;
759            }
760            self.asked.push((key, over));
761            if !fetched.handed.iter().any(|(held, _)| *held == key) {
762                self.unread.insert(key);
763            }
764        }
765        self.fetched.extend(fetched.handed);
766    }
767
768    /// Whether this change already read `key` over `over`, or could not.
769    fn holds(&self, key: Hash, over: Extent) -> bool {
770        self.unread.contains(&key)
771            || self
772                .asked
773                .iter()
774                .any(|(k, e)| *k == key && e.intersect(over) == over)
775    }
776}
777
778/// `build`'s change, the stream held only while each try runs, between two blocks: an exact
779/// stream pulled once its edit is done plays it where issued. `build` runs again whenever the
780/// stream changed under it.
781pub async fn change<E: From<EngineError>, B: Backend>(
782    stream: &RefCell<Stream>,
783    build: impl FnMut(&Stream) -> Result<Change, E>,
784    tier: &Tier<B>,
785) -> Result<Changed, E> {
786    let target = stream.borrow().call.target().to_string();
787    let call = tier.call(Kind::Edit, &target, Phase::Typing);
788    let changed = changing(stream, build, (tier, &call)).await;
789    if changed.is_ok() {
790        call.end(Phase::Done);
791    }
792    changed
793}
794
795async fn changing<E: From<EngineError>, B: Backend>(
796    stream: &RefCell<Stream>,
797    mut build: impl FnMut(&Stream) -> Result<Change, E>,
798    (tier, call): (&Tier<B>, &Call),
799) -> Result<Changed, E> {
800    let mut local = Local {
801        issued: stream.borrow().driver.at,
802        ..Local::default()
803    };
804    let round = tier.begin();
805    stream.borrow_mut().driver.recording.begin();
806    loop {
807        let change = build(&stream.borrow())?;
808        let prospect = match stream.borrow().prospect(change) {
809            Ok(prospect) => prospect,
810            Err(answer) => return Ok(answer),
811        };
812        let generation = stream.borrow().generation;
813        loop {
814            if stream.borrow().generation != generation {
815                break;
816            }
817            let attempt =
818                stream
819                    .borrow_mut()
820                    .attempt(&prospect, &mut local, (tier.memory(), round))?;
821            match attempt {
822                Attempt::Landed(answer) => return Ok(answer),
823                Attempt::Moved => break,
824                Attempt::Asks(keys) => {
825                    call.phase(Phase::LookingUp);
826                    local.look(keys, tier, round).await
827                }
828                Attempt::Reads(wants) => {
829                    call.phase(Phase::Fetching);
830                    local.read(wants, tier).await
831                }
832            }
833        }
834    }
835}
836
837/// What the next second of `stream` reads off memory, as far as one fetch reaches; a block
838/// reading samples not ready computes them, or, live, starts them silent.
839pub async fn fetch<B: Backend>(stream: &RefCell<Stream>, tier: &Tier<B>) {
840    let needs = stream.borrow().needs();
841    let fetched = tier.fetch(&needs).await;
842    let mut stream = stream.borrow_mut();
843    for (key, parts) in fetched.handed {
844        stream.driver.value_graph.took(key, &parts);
845    }
846}
847
848fn ahead(at: i64, rate: u32) -> Extent {
849    Extent::new(at, at.saturating_add(i64::from(rate)))
850}
851
852fn widens(plays: usize, width: usize) -> Result<(), EngineError> {
853    match plays == width || plays == 1 {
854        true => Ok(()),
855        false => Err(refused(
856            "engine.stream_width",
857            format!("this plays {plays} channel(s), and the stream plays {width}"),
858            "play as many channels as the stream, or one, or open a new stream for it",
859        )),
860    }
861}
862
863fn blocked(config: &StreamConfig) -> Result<&StreamConfig, EngineError> {
864    match config.block {
865        0 => Err(refusal("a block of no samples".to_string())),
866        _ => Ok(config),
867    }
868}
869
870pub(super) fn refusal(what: String) -> EngineError {
871    refused(
872        "engine.no_stream",
873        format!("this target opens no stream: {what}"),
874        "stream an expression over the nodes the composition defines",
875    )
876}
877
878fn refused(code: &str, message: String, help: &str) -> EngineError {
879    EngineError::refused(Diagnostic {
880        code: code.to_string(),
881        message,
882        location: Located::at(STREAMED, None),
883        help: help.to_string(),
884    })
885}
886
887/// A mono block in each of `width` channels; any other as it is.
888fn widened(mut block: Buffer, width: usize) -> Buffer {
889    if block.planes.len() == 1 {
890        let copies = vec![block.planes[0].clone(); width.saturating_sub(1)];
891        block.planes.extend(copies);
892    }
893    block
894}