mod answer;
mod drive;
mod end;
mod run;
mod slots;
mod stream;
mod terms;
pub mod until;
pub(crate) mod value_graph;
mod volatile;
mod world;
use std::collections::BTreeMap;
use std::num::NonZeroUsize;
use std::sync::Arc;
use sva_ast::Graph;
use sva_formula::{NodeId, SpectralSum};
use sva_samples::{Buffer, Extent, FilterTrace, Frames, Label, PSYCHOACOUSTIC_V1, Profile};
use crate::bindings::Binding;
use crate::cache::{CacheStats, Memory, Recording, Stored, Tier, now};
use crate::error::{Diagnostic, EngineError, Located};
use crate::instantiate;
use crate::query::Ask;
use crate::refs;
use crate::schedule::{self, Schedule};
use crate::typing::{self, Typing};
use end::Ending;
use value_graph::ValueGraph;
use value_graph::support::Supports;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Range {
pub start: Option<i64>,
pub end: Option<i64>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RenderConfig {
pub rate: u32,
pub range: Range,
pub until: Option<Until>,
pub profile: Profile,
pub asks: Vec<Ask>,
pub volatile: Vec<String>,
pub out: Out,
pub threads: NonZeroUsize,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Out {
#[default]
Kept,
Dropped,
}
impl RenderConfig {
pub fn at(rate: u32) -> RenderConfig {
RenderConfig {
rate,
range: Range::default(),
until: None,
profile: PSYCHOACOUSTIC_V1,
asks: Vec::new(),
volatile: Vec::new(),
out: Out::Kept,
threads: crate::threads::default_threads(),
}
}
pub fn seconds(rate: u32, secs: f64) -> RenderConfig {
let end = (secs * f64::from(rate)).round() as i64;
RenderConfig {
range: Range {
start: Some(0),
end: Some(end),
},
..RenderConfig::at(rate)
}
}
pub fn asking(mut self, asks: Vec<Ask>) -> RenderConfig {
self.asks = asks;
self
}
}
pub use answer::{answer, answer_buffer, sketch_atom};
pub use drive::Work;
pub use run::{Abandon, Never, Session, render_in, render_over};
pub use stream::{
Built, Change, Changed, Counts, LATEST, Placed, Stream, StreamConfig, change, fetch,
};
pub use terms::{Handle, NOTES};
pub use until::Until;
pub use world::STREAMED;
pub struct Render {
pub root: NodeId,
pub tys: Typing,
pub buffers: BTreeMap<NodeId, Buffer>,
pub frames: BTreeMap<NodeId, Frames>,
pub symbolic: BTreeMap<NodeId, SpectralSum>,
pub labels: BTreeMap<NodeId, Label>,
pub traces: Vec<FilterTrace>,
pub config: RenderConfig,
pub schedule: Schedule,
pub bindings: BTreeMap<NodeId, Vec<Binding>>,
pub cache_stats: Option<CacheStats>,
pub held_bytes: usize,
pub range: Option<Extent>,
pub(crate) unranged: Option<EngineError>,
pub(crate) value_graph: Option<ValueGraph>,
computed_samples: u64,
pub(crate) unslotted: Option<String>,
pub(crate) stand_in_typed: Vec<String>,
pub(crate) treated_as_silent_from_sample: Option<i64>,
pub(crate) memory: Memory,
}
impl Render {
pub(crate) fn shell(
(tys, root): (Typing, NodeId),
(config, schedule): (RenderConfig, Schedule),
memory: Memory,
) -> Self {
Render {
root,
tys,
buffers: BTreeMap::new(),
frames: BTreeMap::new(),
symbolic: BTreeMap::new(),
labels: BTreeMap::new(),
traces: Vec::new(),
config,
schedule,
bindings: BTreeMap::new(),
cache_stats: None,
held_bytes: 0,
range: None,
unranged: None,
value_graph: None,
computed_samples: 0,
unslotted: None,
stand_in_typed: Vec::new(),
treated_as_silent_from_sample: None,
memory,
}
}
pub fn work(&self) -> Work {
Work {
samples: self.range.map_or(0, |range| range.len() as u64),
computed_samples: self.computed_samples,
}
}
pub fn output(&self, node: NodeId) -> Result<Buffer, EngineError> {
match (self.buffers.get(&node), self.range) {
(Some(held), Some(range)) => Ok(held.over(range, held.extent())),
_ => Err(answer::unheld(self, node)),
}
}
pub fn buffer(&self, node: NodeId) -> Option<&Buffer> {
self.buffers.get(&node)
}
pub fn id(&self, path: &str) -> Option<NodeId> {
self.tys.id(path)
}
pub fn node(&self, path: &str) -> Result<NodeId, EngineError> {
self.tys.resolve(path)
}
pub fn evaluated(&self, node: NodeId) -> Vec<Extent> {
let Some(value_graph) = &self.value_graph else {
return Vec::new();
};
value_graph
.of(node)
.map_or(Vec::new(), |at| value_graph.values[at].evaluated.clone())
}
pub fn alias_oversample(&self, node: NodeId) -> Option<u32> {
self.config
.asks
.iter()
.find_map(|ask| match ask.representation {
crate::query::Representation::Alias { oversample }
if self.node(&ask.node).is_ok_and(|asked| asked == node) =>
{
Some(oversample)
}
_ => None,
})
}
}
pub fn render(
graph: &Graph,
target: &str,
config: RenderConfig,
tier: &Tier,
) -> Result<Render, EngineError> {
now(render_over(graph, target, config, tier))
}
fn closed(held: &mut Render) -> Result<(), EngineError> {
scored(held)?;
compose_read(held);
stamp(held);
Ok(())
}
pub(crate) struct Prepared {
pub(crate) instances: instantiate::Instances,
pub(crate) tys: Typing,
pub(crate) root: NodeId,
}
pub(crate) fn prepared(graph: &Graph, target: &str, rate: u32) -> Result<Prepared, EngineError> {
let instances = instantiate::instantiate(graph, target, rate)?;
let held = instances.instance_of(target)?;
let order = schedule::schedule_from(&instances, std::slice::from_ref(&held))?;
let tys = typing::infer_all(&instances, &order)?;
let root = tys
.id(&held)
.ok_or_else(|| EngineError::UnknownNode(held.clone()))?;
Ok(Prepared {
instances,
tys,
root,
})
}
fn planned(
(graph, target): (&Graph, &str),
prepared: Prepared,
(config, memory): (RenderConfig, Memory),
) -> Result<Render, EngineError> {
let Prepared {
instances,
tys,
root,
} = prepared;
let config = (config, &mut None, memory);
let none = (None, &BTreeMap::new());
planned_over((graph, target, &instances), (tys, root), config, none)
}
fn planned_over(
(graph, target, instances): (&Graph, &str, &instantiate::Instances),
(tys, root): (Typing, NodeId),
(config, stand_in, memory): (RenderConfig, &mut Option<world::World>, Memory),
(decided, hits): (Option<end::End>, &BTreeMap<NodeId, Arc<Stored>>),
) -> Result<Render, EngineError> {
let schedule = schedule::plan(&tys, root, &config.asks);
let bindings = tys
.paths()
.filter_map(|(path, id)| Some((id, resolved(instances, path)?)))
.collect();
let mut held = Render::shell((tys, root), (config, schedule), memory);
held.bindings = bindings;
ranged(&mut held, (decided, hits))?;
let volatile = volatile::mark((graph, instances), &held, (target, stand_in))?;
if let Some(value_graph) = &mut held.value_graph {
value_graph.slots(volatile.slots());
}
held.unslotted = volatile.unslotted.clone();
held.stand_in_typed = volatile.typed;
Ok(held)
}
pub fn plan(graph: &Graph, target: &str, config: RenderConfig) -> Result<Render, EngineError> {
planned(
(graph, target),
prepared(graph, target, config.rate)?,
(config, Memory::holding(0)),
)
}
pub fn ends(
graph: &Graph,
roots: &[String],
config: &RenderConfig,
) -> Result<Vec<Option<i64>>, EngineError> {
let (instances, named) = instantiate::from_roots(graph, roots, config.rate)?;
let order = schedule::schedule_from(&instances, &named)?;
let tys = typing::infer_all(&instances, &order)?;
let supports = Supports::new(&tys);
let ending = Ending::new(&tys, &config.profile, &supports);
named
.iter()
.map(|held| {
let id = tys
.id(held)
.ok_or_else(|| EngineError::UnknownNode(held.clone()))?;
Ok(default_end(ending.of(id).support))
})
.collect()
}
pub(crate) fn ended(
tys: &Typing,
root: NodeId,
mut config: RenderConfig,
) -> (RenderConfig, Option<end::End>) {
if config.range.end.is_some() {
return (config, None);
}
let supports = Supports::new(tys);
let end = Ending::new(tys, &config.profile, &supports).of(root);
config.range.end = default_end(end.support);
(config, Some(end))
}
fn ranged(
held: &mut Render,
(decided, hits): (Option<end::End>, &BTreeMap<NodeId, Arc<Stored>>),
) -> Result<(), EngineError> {
let envelope = held.config.asks.iter().any(|ask| {
matches!(
ask.representation,
crate::query::Representation::Envelope { .. }
)
});
let tabled = materializes(held);
if !tabled && !envelope {
return Ok(());
}
let supports = Supports::new(&held.tys);
let end = match (held.config.range.end, decided) {
(_, Some(end)) => end,
(Some(_), None) => Ending::new(&held.tys, &held.config.profile, &supports).exact(held.root),
(None, None) => Ending::new(&held.tys, &held.config.profile, &supports).of(held.root),
};
held.treated_as_silent_from_sample = end.treated_as_silent_from_sample;
let support = end.support;
let range = range_over(
(&held.config, held.tys.name(held.root)),
support,
Ends::Refused,
);
if !tabled {
match range {
Ok(range) => held.range = Some(range),
Err(refused) => held.unranged = Some(refused),
}
return Ok(());
}
held.range = Some(range?);
let found = supports.into_memo();
let wanted: Vec<NodeId> = held.schedule.wanted.clone();
let root = (held.root, wanted.as_slice());
let value_graph = ValueGraph::bounded(&held.tys, root, &held.config.profile, (found, hits))?;
value_graph.refuse_endless(held.range.expect("a range was decided"))?;
held.value_graph = Some(value_graph);
Ok(())
}
fn materializes(held: &Render) -> bool {
!held.schedule.wanted.is_empty()
}
pub(crate) enum Ends {
Refused,
Pulled,
}
pub(crate) fn range_over(
(config, name): (&RenderConfig, &str),
support: Extent,
ends: Ends,
) -> Result<Extent, EngineError> {
let start = config.range.start.unwrap_or_else(|| default_start(support));
let end = match config.range.end.or(default_end(support)) {
Some(end) => end,
None => match ends {
Ends::Pulled => i64::MAX,
Ends::Refused => return Err(endless(name)),
},
};
Ok(Extent::new(start, end.max(start)))
}
fn default_start(support: Extent) -> i64 {
match support.is_empty() || support.start == i64::MIN || support.start > 0 {
true => 0,
false => support.start,
}
}
fn default_end(support: Extent) -> Option<i64> {
(support.end != i64::MAX).then_some(support.end)
}
fn endless(name: &str) -> EngineError {
EngineError::refused(Diagnostic {
code: "render.no_end".to_string(),
message: format!(
"`{name}` is read over an interval with no end, and its support never ends"
),
location: Located::at(name, None),
help: "give the interval an end, as `[0, 2s]`, or crop it".to_string(),
})
}
#[cfg(test)]
fn pulled(held: &mut Render, memo: (&Memory, Recording)) -> Result<(), EngineError> {
if let Some(mut driver) = driving(held, memo)? {
while driver.pull()? {}
drove(held, driver);
}
Ok(())
}
fn driving(
held: &mut Render,
(memory, recording): (&Memory, Recording),
) -> Result<Option<drive::Driver>, EngineError> {
let (Some(value_graph), Some(range)) = (held.value_graph.take(), held.range) else {
return Ok(None);
};
if !materializes(held) {
held.value_graph = Some(value_graph);
return Ok(None);
}
Ok(Some(drive::Driver::new(
value_graph,
range,
(sva_samples::BLOCK as usize).saturating_mul(held.config.threads.get()),
&held.config,
(memory.clone(), recording),
)))
}
pub(crate) fn dropped(config: &RenderConfig) -> bool {
config.out == Out::Dropped && config.asks.is_empty()
}
fn drove(held: &mut Render, driver: drive::Driver) {
let keep = !dropped(&held.config);
let range = held.range.expect("a pulled render has a range");
held.held_bytes = driver.most_bytes();
held.computed_samples = driver.work.computed_samples;
held.cache_stats = Some(driver.recording.stats(&driver.memory));
if let Some(stop) = driver.stop().filter(|stop| *stop < range.end) {
held.range = Some(Extent::new(range.start, stop));
}
let range = held.range.expect("a pulled render has a range");
let value_graph = driver.value_graph;
let wanted = held.schedule.wanted.iter().filter(|_| keep);
for (id, at) in wanted.map(|id| (*id, value_graph.of(*id))) {
let Some(at) = at else {
continue;
};
match &value_graph.values[at].holding {
value_graph::Holding::Frames(Some(frames)) => {
held.frames.insert(id, (**frames).clone());
}
_ => {
held.buffers.insert(id, value_graph.samples(at, range));
held.labels.insert(id, value_graph.label(at));
}
}
}
held.value_graph = Some(value_graph);
}
pub(crate) fn finer(
render: &Render,
node: NodeId,
fine: u32,
over: Extent,
) -> Result<Buffer, EngineError> {
let profile = &render.config.profile;
let mut value_graph = ValueGraph::finer(&render.tys, node, &[node], profile, i128::from(fine))?;
let at = value_graph.root;
let memory = &render.memory;
let threads = render.config.threads;
value_graph.pull(over, (memory, &mut Recording::over(memory)), threads)?;
let mut held = value_graph.samples(at, over);
held.rate = render.config.rate * fine;
Ok(held)
}
fn scored(held: &mut Render) -> Result<(), EngineError> {
let asked: Vec<NodeId> = held
.labels
.keys()
.copied()
.filter(|id| held.alias_oversample(*id).is_some())
.collect();
for id in asked {
let sva_samples::Detail::Point {
rule,
alias_db: None,
tail_db,
} = held.labels[&id].detail
else {
continue;
};
let buffer = held.output(id)?;
let alias_db = Some(answer::alias_db(held, id, &buffer)?);
let label = held.labels.get_mut(&id).expect("an asked label");
label.detail = sva_samples::Detail::Point {
rule,
alias_db,
tail_db,
};
}
Ok(())
}
#[cfg(test)]
pub(crate) fn render_apart(
graph: &Graph,
target: &str,
config: RenderConfig,
) -> Result<Render, EngineError> {
let mut held = planned(
(graph, target),
prepared(graph, target, config.rate)?,
(config, Memory::holding(0)),
)?;
if let (Some(range), Some(_)) = (held.range, &held.value_graph) {
let wanted = held.schedule.wanted.clone();
let value_graph = ValueGraph::apart(&held.tys, held.root, &wanted, &held.config.profile)?;
value_graph.refuse_endless(range)?;
held.value_graph = Some(value_graph);
}
let memory = held.memory.clone();
pulled(&mut held, (&memory, Recording::over(&memory)))?;
Ok(held)
}
pub(crate) fn sampled(render: &Render, node: NodeId, over: Extent) -> Result<Buffer, EngineError> {
let mut value_graph = ValueGraph::build(&render.tys, node, &[node], &render.config.profile)?;
let at = value_graph.root;
let memory = &render.memory;
let threads = render.config.threads;
value_graph.pull(over, (memory, &mut Recording::over(memory)), threads)?;
Ok(value_graph.samples(at, over))
}
fn stamp(held: &mut Render) {
let root = held.root;
let Some(label) = held.labels.remove(&root) else {
return;
};
let treated_as_silent_from_sample = held
.treated_as_silent_from_sample
.map(|at| (held.tys.name(root).to_string(), at));
let cutting = sva_samples::CuttingBelowSilenceThreshold {
silence_threshold_dbfs: held.config.profile.silence_threshold_dbfs,
treated_as_silent_from_sample: treated_as_silent_from_sample.into_iter().collect(),
};
let label = sva_samples::Label {
rate: held.config.rate,
moved: held.value_graph.as_ref().map(ValueGraph::moved),
cutting_below_silence_threshold: Some(cutting),
..label
};
held.labels.insert(root, label);
}
fn compose_read(held: &mut Render) {
for id in held.schedule.compose.clone() {
if held.symbolic.contains_key(&id) {
continue;
}
if let Ok(sum) = refs::spectral_sum_of(&held.tys, id, held.tys.var(id)) {
held.symbolic.insert(id, sum);
}
}
}
fn resolved(instances: &instantiate::Instances, path: &str) -> Option<Vec<Binding>> {
Some(
instances
.bindings(path)?
.into_iter()
.map(|(name, expr, cx)| Binding {
name: name.to_string(),
source: instances.render(expr, cx),
})
.collect(),
)
}