1mod answer;
4mod drive;
5mod end;
6mod run;
7mod slots;
8mod stream;
9mod terms;
10pub mod until;
11pub(crate) mod value_graph;
12mod volatile;
13mod world;
14
15use std::collections::BTreeMap;
16use std::num::NonZeroUsize;
17use std::sync::Arc;
18
19use sva_ast::Graph;
20use sva_formula::{NodeId, SpectralSum};
21use sva_samples::{Buffer, Extent, FilterTrace, Frames, Label, PSYCHOACOUSTIC_V1, Profile};
22
23use crate::bindings::Binding;
24use crate::cache::{CacheStats, Memory, Recording, Stored, Tier, now};
25use crate::error::{Diagnostic, EngineError, Located};
26use crate::instantiate;
27use crate::query::Ask;
28use crate::refs;
29use crate::schedule::{self, Schedule};
30use crate::typing::{self, Typing};
31use end::Ending;
32use value_graph::ValueGraph;
33use value_graph::support::Supports;
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
37pub struct Range {
38 pub start: Option<i64>,
39 pub end: Option<i64>,
40}
41
42#[derive(Clone, Debug, PartialEq)]
43pub struct RenderConfig {
44 pub rate: u32,
45 pub range: Range,
46 pub until: Option<Until>,
48 pub profile: Profile,
49 pub asks: Vec<Ask>,
51 pub volatile: Vec<String>,
53 pub out: Out,
54 pub threads: NonZeroUsize,
56}
57
58#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
61pub enum Out {
62 #[default]
63 Kept,
64 Dropped,
65}
66
67impl RenderConfig {
68 pub fn at(rate: u32) -> RenderConfig {
69 RenderConfig {
70 rate,
71 range: Range::default(),
72 until: None,
73 profile: PSYCHOACOUSTIC_V1,
74 asks: Vec::new(),
75 volatile: Vec::new(),
76 out: Out::Kept,
77 threads: crate::threads::default_threads(),
78 }
79 }
80
81 pub fn seconds(rate: u32, secs: f64) -> RenderConfig {
82 let end = (secs * f64::from(rate)).round() as i64;
83 RenderConfig {
84 range: Range {
85 start: Some(0),
86 end: Some(end),
87 },
88 ..RenderConfig::at(rate)
89 }
90 }
91
92 pub fn asking(mut self, asks: Vec<Ask>) -> RenderConfig {
93 self.asks = asks;
94 self
95 }
96}
97
98pub use answer::{answer, answer_buffer, sketch_atom};
99pub use drive::Work;
100pub use run::{Abandon, Never, Session, render_in, render_over};
101pub use stream::{
102 Built, Change, Changed, Counts, LATEST, Placed, Stream, StreamConfig, change, fetch,
103};
104pub use terms::{Handle, NOTES};
105pub use until::Until;
106pub use world::STREAMED;
107
108pub struct Render {
109 pub root: NodeId,
110 pub tys: Typing,
111 pub buffers: BTreeMap<NodeId, Buffer>,
112 pub frames: BTreeMap<NodeId, Frames>,
113 pub symbolic: BTreeMap<NodeId, SpectralSum>,
114 pub labels: BTreeMap<NodeId, Label>,
115 pub traces: Vec<FilterTrace>,
116 pub config: RenderConfig,
117 pub schedule: Schedule,
118 pub bindings: BTreeMap<NodeId, Vec<Binding>>,
119 pub cache_stats: Option<CacheStats>,
120 pub held_bytes: usize,
122 pub range: Option<Extent>,
124 pub(crate) unranged: Option<EngineError>,
125 pub(crate) value_graph: Option<ValueGraph>,
127 computed_samples: u64,
129 pub(crate) unslotted: Option<String>,
130 pub(crate) stand_in_typed: Vec<String>,
132 pub(crate) treated_as_silent_from_sample: Option<i64>,
133 pub(crate) memory: Memory,
135}
136
137impl Render {
138 pub(crate) fn shell(
139 (tys, root): (Typing, NodeId),
140 (config, schedule): (RenderConfig, Schedule),
141 memory: Memory,
142 ) -> Self {
143 Render {
144 root,
145 tys,
146 buffers: BTreeMap::new(),
147 frames: BTreeMap::new(),
148 symbolic: BTreeMap::new(),
149 labels: BTreeMap::new(),
150 traces: Vec::new(),
151 config,
152 schedule,
153 bindings: BTreeMap::new(),
154 cache_stats: None,
155 held_bytes: 0,
156 range: None,
157 unranged: None,
158 value_graph: None,
159 computed_samples: 0,
160 unslotted: None,
161 stand_in_typed: Vec::new(),
162 treated_as_silent_from_sample: None,
163 memory,
164 }
165 }
166
167 pub fn work(&self) -> Work {
169 Work {
170 samples: self.range.map_or(0, |range| range.len() as u64),
171 computed_samples: self.computed_samples,
172 }
173 }
174
175 pub fn output(&self, node: NodeId) -> Result<Buffer, EngineError> {
177 match (self.buffers.get(&node), self.range) {
178 (Some(held), Some(range)) => Ok(held.over(range, held.extent())),
179 _ => Err(answer::unheld(self, node)),
180 }
181 }
182
183 pub fn buffer(&self, node: NodeId) -> Option<&Buffer> {
184 self.buffers.get(&node)
185 }
186
187 pub fn id(&self, path: &str) -> Option<NodeId> {
188 self.tys.id(path)
189 }
190
191 pub fn node(&self, path: &str) -> Result<NodeId, EngineError> {
193 self.tys.resolve(path)
194 }
195
196 pub fn evaluated(&self, node: NodeId) -> Vec<Extent> {
199 let Some(value_graph) = &self.value_graph else {
200 return Vec::new();
201 };
202 value_graph
203 .of(node)
204 .map_or(Vec::new(), |at| value_graph.values[at].evaluated.clone())
205 }
206
207 pub fn alias_oversample(&self, node: NodeId) -> Option<u32> {
209 self.config
210 .asks
211 .iter()
212 .find_map(|ask| match ask.representation {
213 crate::query::Representation::Alias { oversample }
214 if self.node(&ask.node).is_ok_and(|asked| asked == node) =>
215 {
216 Some(oversample)
217 }
218 _ => None,
219 })
220 }
221}
222
223pub fn render(
225 graph: &Graph,
226 target: &str,
227 config: RenderConfig,
228 tier: &Tier,
229) -> Result<Render, EngineError> {
230 now(render_over(graph, target, config, tier))
231}
232
233fn closed(held: &mut Render) -> Result<(), EngineError> {
234 scored(held)?;
235 compose_read(held);
236 stamp(held);
237 Ok(())
238}
239
240pub(crate) struct Prepared {
241 pub(crate) instances: instantiate::Instances,
242 pub(crate) tys: Typing,
243 pub(crate) root: NodeId,
244}
245
246pub(crate) fn prepared(graph: &Graph, target: &str, rate: u32) -> Result<Prepared, EngineError> {
247 let instances = instantiate::instantiate(graph, target, rate)?;
248 let held = instances.instance_of(target)?;
249 let order = schedule::schedule_from(&instances, std::slice::from_ref(&held))?;
250 let tys = typing::infer_all(&instances, &order)?;
251 let root = tys
252 .id(&held)
253 .ok_or_else(|| EngineError::UnknownNode(held.clone()))?;
254 Ok(Prepared {
255 instances,
256 tys,
257 root,
258 })
259}
260
261fn planned(
262 (graph, target): (&Graph, &str),
263 prepared: Prepared,
264 (config, memory): (RenderConfig, Memory),
265) -> Result<Render, EngineError> {
266 let Prepared {
267 instances,
268 tys,
269 root,
270 } = prepared;
271 let config = (config, &mut None, memory);
272 let none = (None, &BTreeMap::new());
273 planned_over((graph, target, &instances), (tys, root), config, none)
274}
275
276fn planned_over(
277 (graph, target, instances): (&Graph, &str, &instantiate::Instances),
278 (tys, root): (Typing, NodeId),
279 (config, stand_in, memory): (RenderConfig, &mut Option<world::World>, Memory),
280 (decided, hits): (Option<end::End>, &BTreeMap<NodeId, Arc<Stored>>),
281) -> Result<Render, EngineError> {
282 let schedule = schedule::plan(&tys, root, &config.asks);
283 let bindings = tys
284 .paths()
285 .filter_map(|(path, id)| Some((id, resolved(instances, path)?)))
286 .collect();
287 let mut held = Render::shell((tys, root), (config, schedule), memory);
288 held.bindings = bindings;
289 ranged(&mut held, (decided, hits))?;
290 let volatile = volatile::mark((graph, instances), &held, (target, stand_in))?;
291 if let Some(value_graph) = &mut held.value_graph {
292 value_graph.slots(volatile.slots());
293 }
294 held.unslotted = volatile.unslotted.clone();
295 held.stand_in_typed = volatile.typed;
296 Ok(held)
297}
298
299pub fn plan(graph: &Graph, target: &str, config: RenderConfig) -> Result<Render, EngineError> {
301 planned(
302 (graph, target),
303 prepared(graph, target, config.rate)?,
304 (config, Memory::holding(0)),
305 )
306}
307
308pub fn ends(
310 graph: &Graph,
311 roots: &[String],
312 config: &RenderConfig,
313) -> Result<Vec<Option<i64>>, EngineError> {
314 let (instances, named) = instantiate::from_roots(graph, roots, config.rate)?;
315 let order = schedule::schedule_from(&instances, &named)?;
316 let tys = typing::infer_all(&instances, &order)?;
317 let supports = Supports::new(&tys);
318 let ending = Ending::new(&tys, &config.profile, &supports);
319 named
320 .iter()
321 .map(|held| {
322 let id = tys
323 .id(held)
324 .ok_or_else(|| EngineError::UnknownNode(held.clone()))?;
325 Ok(default_end(ending.of(id).support))
326 })
327 .collect()
328}
329
330pub(crate) fn ended(
332 tys: &Typing,
333 root: NodeId,
334 mut config: RenderConfig,
335) -> (RenderConfig, Option<end::End>) {
336 if config.range.end.is_some() {
337 return (config, None);
338 }
339 let supports = Supports::new(tys);
340 let end = Ending::new(tys, &config.profile, &supports).of(root);
341 config.range.end = default_end(end.support);
342 (config, Some(end))
343}
344
345fn ranged(
348 held: &mut Render,
349 (decided, hits): (Option<end::End>, &BTreeMap<NodeId, Arc<Stored>>),
350) -> Result<(), EngineError> {
351 let envelope = held.config.asks.iter().any(|ask| {
352 matches!(
353 ask.representation,
354 crate::query::Representation::Envelope { .. }
355 )
356 });
357 let tabled = materializes(held);
358 if !tabled && !envelope {
359 return Ok(());
360 }
361 let supports = Supports::new(&held.tys);
362 let end = match (held.config.range.end, decided) {
363 (_, Some(end)) => end,
364 (Some(_), None) => Ending::new(&held.tys, &held.config.profile, &supports).exact(held.root),
365 (None, None) => Ending::new(&held.tys, &held.config.profile, &supports).of(held.root),
366 };
367 held.treated_as_silent_from_sample = end.treated_as_silent_from_sample;
368 let support = end.support;
369 let range = range_over(
370 (&held.config, held.tys.name(held.root)),
371 support,
372 Ends::Refused,
373 );
374 if !tabled {
375 match range {
376 Ok(range) => held.range = Some(range),
377 Err(refused) => held.unranged = Some(refused),
378 }
379 return Ok(());
380 }
381 held.range = Some(range?);
382 let found = supports.into_memo();
383 let wanted: Vec<NodeId> = held.schedule.wanted.clone();
384 let root = (held.root, wanted.as_slice());
385 let value_graph = ValueGraph::bounded(&held.tys, root, &held.config.profile, (found, hits))?;
386 value_graph.refuse_endless(held.range.expect("a range was decided"))?;
387 held.value_graph = Some(value_graph);
388 Ok(())
389}
390
391fn materializes(held: &Render) -> bool {
392 !held.schedule.wanted.is_empty()
393}
394
395pub(crate) enum Ends {
396 Refused,
397 Pulled,
398}
399
400pub(crate) fn range_over(
402 (config, name): (&RenderConfig, &str),
403 support: Extent,
404 ends: Ends,
405) -> Result<Extent, EngineError> {
406 let start = config.range.start.unwrap_or_else(|| default_start(support));
407 let end = match config.range.end.or(default_end(support)) {
408 Some(end) => end,
409 None => match ends {
410 Ends::Pulled => i64::MAX,
411 Ends::Refused => return Err(endless(name)),
412 },
413 };
414 Ok(Extent::new(start, end.max(start)))
415}
416
417fn default_start(support: Extent) -> i64 {
418 match support.is_empty() || support.start == i64::MIN || support.start > 0 {
419 true => 0,
420 false => support.start,
421 }
422}
423
424fn default_end(support: Extent) -> Option<i64> {
425 (support.end != i64::MAX).then_some(support.end)
426}
427
428fn endless(name: &str) -> EngineError {
429 EngineError::refused(Diagnostic {
430 code: "render.no_end".to_string(),
431 message: format!(
432 "`{name}` is read over an interval with no end, and its support never ends"
433 ),
434 location: Located::at(name, None),
435 help: "give the interval an end, as `[0, 2s]`, or crop it".to_string(),
436 })
437}
438
439#[cfg(test)]
441fn pulled(held: &mut Render, memo: (&Memory, Recording)) -> Result<(), EngineError> {
442 if let Some(mut driver) = driving(held, memo)? {
443 while driver.pull()? {}
444 drove(held, driver);
445 }
446 Ok(())
447}
448
449fn driving(
451 held: &mut Render,
452 (memory, recording): (&Memory, Recording),
453) -> Result<Option<drive::Driver>, EngineError> {
454 let (Some(value_graph), Some(range)) = (held.value_graph.take(), held.range) else {
455 return Ok(None);
456 };
457 if !materializes(held) {
458 held.value_graph = Some(value_graph);
459 return Ok(None);
460 }
461 Ok(Some(drive::Driver::new(
462 value_graph,
463 range,
464 (sva_samples::BLOCK as usize).saturating_mul(held.config.threads.get()),
465 &held.config,
466 (memory.clone(), recording),
467 )))
468}
469
470pub(crate) fn dropped(config: &RenderConfig) -> bool {
471 config.out == Out::Dropped && config.asks.is_empty()
472}
473
474fn drove(held: &mut Render, driver: drive::Driver) {
475 let keep = !dropped(&held.config);
476 let range = held.range.expect("a pulled render has a range");
477 held.held_bytes = driver.most_bytes();
478 held.computed_samples = driver.work.computed_samples;
479 held.cache_stats = Some(driver.recording.stats(&driver.memory));
480 if let Some(stop) = driver.stop().filter(|stop| *stop < range.end) {
481 held.range = Some(Extent::new(range.start, stop));
482 }
483 let range = held.range.expect("a pulled render has a range");
484 let value_graph = driver.value_graph;
485 let wanted = held.schedule.wanted.iter().filter(|_| keep);
486 for (id, at) in wanted.map(|id| (*id, value_graph.of(*id))) {
487 let Some(at) = at else {
488 continue;
489 };
490 match &value_graph.values[at].holding {
491 value_graph::Holding::Frames(Some(frames)) => {
492 held.frames.insert(id, (**frames).clone());
493 }
494 _ => {
495 held.buffers.insert(id, value_graph.samples(at, range));
496 held.labels.insert(id, value_graph.label(at));
497 }
498 }
499 }
500 held.value_graph = Some(value_graph);
501}
502
503pub(crate) fn finer(
506 render: &Render,
507 node: NodeId,
508 fine: u32,
509 over: Extent,
510) -> Result<Buffer, EngineError> {
511 let profile = &render.config.profile;
512 let mut value_graph = ValueGraph::finer(&render.tys, node, &[node], profile, i128::from(fine))?;
513 let at = value_graph.root;
514 let memory = &render.memory;
515 let threads = render.config.threads;
516 value_graph.pull(over, (memory, &mut Recording::over(memory)), threads)?;
517 let mut held = value_graph.samples(at, over);
518 held.rate = render.config.rate * fine;
519 Ok(held)
520}
521
522fn scored(held: &mut Render) -> Result<(), EngineError> {
524 let asked: Vec<NodeId> = held
525 .labels
526 .keys()
527 .copied()
528 .filter(|id| held.alias_oversample(*id).is_some())
529 .collect();
530 for id in asked {
531 let sva_samples::Detail::Point {
532 rule,
533 alias_db: None,
534 tail_db,
535 } = held.labels[&id].detail
536 else {
537 continue;
538 };
539 let buffer = held.output(id)?;
540 let alias_db = Some(answer::alias_db(held, id, &buffer)?);
541 let label = held.labels.get_mut(&id).expect("an asked label");
542 label.detail = sva_samples::Detail::Point {
543 rule,
544 alias_db,
545 tail_db,
546 };
547 }
548 Ok(())
549}
550
551#[cfg(test)]
553pub(crate) fn render_apart(
554 graph: &Graph,
555 target: &str,
556 config: RenderConfig,
557) -> Result<Render, EngineError> {
558 let mut held = planned(
559 (graph, target),
560 prepared(graph, target, config.rate)?,
561 (config, Memory::holding(0)),
562 )?;
563 if let (Some(range), Some(_)) = (held.range, &held.value_graph) {
564 let wanted = held.schedule.wanted.clone();
565 let value_graph = ValueGraph::apart(&held.tys, held.root, &wanted, &held.config.profile)?;
566 value_graph.refuse_endless(range)?;
567 held.value_graph = Some(value_graph);
568 }
569 let memory = held.memory.clone();
570 pulled(&mut held, (&memory, Recording::over(&memory)))?;
571 Ok(held)
572}
573
574pub(crate) fn sampled(render: &Render, node: NodeId, over: Extent) -> Result<Buffer, EngineError> {
575 let mut value_graph = ValueGraph::build(&render.tys, node, &[node], &render.config.profile)?;
576 let at = value_graph.root;
577 let memory = &render.memory;
578 let threads = render.config.threads;
579 value_graph.pull(over, (memory, &mut Recording::over(memory)), threads)?;
580 Ok(value_graph.samples(at, over))
581}
582
583fn stamp(held: &mut Render) {
585 let root = held.root;
586 let Some(label) = held.labels.remove(&root) else {
587 return;
588 };
589 let treated_as_silent_from_sample = held
590 .treated_as_silent_from_sample
591 .map(|at| (held.tys.name(root).to_string(), at));
592 let cutting = sva_samples::CuttingBelowSilenceThreshold {
593 silence_threshold_dbfs: held.config.profile.silence_threshold_dbfs,
594 treated_as_silent_from_sample: treated_as_silent_from_sample.into_iter().collect(),
595 };
596 let label = sva_samples::Label {
597 rate: held.config.rate,
598 moved: held.value_graph.as_ref().map(ValueGraph::moved),
599 cutting_below_silence_threshold: Some(cutting),
600 ..label
601 };
602 held.labels.insert(root, label);
603}
604
605fn compose_read(held: &mut Render) {
608 for id in held.schedule.compose.clone() {
609 if held.symbolic.contains_key(&id) {
610 continue;
611 }
612 if let Ok(sum) = refs::spectral_sum_of(&held.tys, id, held.tys.var(id)) {
613 held.symbolic.insert(id, sum);
614 }
615 }
616}
617
618fn resolved(instances: &instantiate::Instances, path: &str) -> Option<Vec<Binding>> {
619 Some(
620 instances
621 .bindings(path)?
622 .into_iter()
623 .map(|(name, expr, cx)| Binding {
624 name: name.to_string(),
625 source: instances.render(expr, cx),
626 })
627 .collect(),
628 )
629}