1use 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
28pub const LATEST: usize = 256;
30
31#[derive(Clone, Debug, PartialEq)]
32pub struct StreamConfig {
33 pub block: usize,
34 pub channels: Option<usize>,
36 pub render: RenderConfig,
37}
38
39pub struct Stream {
42 config: StreamConfig,
43 world: World,
44 driver: Driver,
45 expr: Expr,
46 terms: Terms,
47 width: usize,
48 heard: BTreeMap<Handle, Heard>,
50 gain: Gained,
51 fading: Vec<Fading>,
53 faded: f64,
54 ending: Option<Option<i64>>,
56 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 pub late: usize,
72 pub terms: usize,
73 pub built: Built,
75 pub demands: usize,
77 pub tier: Counters,
78}
79
80#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
82pub struct Built {
83 pub parsed: usize,
85 pub instances: usize,
86 pub visited: usize,
87 pub typed: usize,
88 pub values: usize,
89 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
690pub enum Change {
692 Target(Graph, Expr),
693 Add(Graph, Expr, Placed),
694 Replace(Handle, Graph, Expr, Placed),
695 Remove(Handle),
697}
698
699#[derive(Clone, Copy, Debug, PartialEq, Eq)]
701pub enum Placed {
702 Written,
703 Landing,
704}
705
706#[derive(Clone, Copy, Debug, PartialEq, Eq)]
708pub enum Changed {
709 Edited,
710 Added(Handle),
711 Held(bool),
712}
713
714struct 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 parsed: usize,
724}
725
726enum Attempt {
728 Landed(Changed),
729 Asks(Vec<Hash>),
730 Reads(Vec<(Hash, Extent)>),
731 Moved,
733}
734
735#[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 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 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
778pub 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
837pub 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
887fn 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}