use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::rc::Rc;
use std::sync::Arc;
use sva_formula::{Hash, Held as Representation, NodeId};
use sva_samples::Extent;
use super::Render;
use super::frontier::Frontier;
use super::table::segments::Segments;
use super::table::{self, Table};
use crate::cache::{Facts, Memory, Offered, Stored};
use crate::schedule;
use crate::typing::Typing;
struct Offer {
at: usize,
stored: Stored,
own: Own,
facts: Facts,
}
enum Own {
Moves,
Shares(Offered),
Copies,
}
pub(crate) struct Offers {
pending: Vec<Offer>,
keys: BTreeMap<usize, Hash>,
range: Extent,
}
impl Offers {
pub(crate) fn of(
held: &mut Render,
(keys, found): (&BTreeMap<String, Hash>, &Frontier),
memory: &Memory,
) -> Offers {
let mut pending = Vec::new();
let range = held.range.unwrap_or(Extent::NOWHERE);
if let Some(table) = &mut held.table {
let tys = &held.tys;
let own = table.cuts_by_name(tys);
let cuts = found.beneath(&|name| own.get(name).map(Vec::as_slice));
let under = Under::of(table);
for (path, key) in keys {
if found.stored.contains_key(path) || !found.visited.contains(path) {
continue;
}
let Some((id, at)) = tys.id(path).and_then(|id| Some((id, table.of(id)?))) else {
continue;
};
if let Some(mut stored) = offerable(table, tys, (id, at), (path, *key), &under) {
let held = cuts.get(path.as_str());
stored.cuts = held.map_or(Vec::new(), |set| set.iter().copied().collect());
let facts = Facts {
slot: table.slot(at),
settled: false,
target: at == table.root,
samples: range.intersect(stored.support).len() as u64,
};
pending.push(Offer {
at,
stored,
own: Own::Moves,
facts,
});
}
}
}
let keys: BTreeMap<usize, Hash> = pending.iter().map(|o| (o.at, o.stored.key)).collect();
if let Some(table) = &mut held.table {
let feet = moves(table);
for offer in &mut pending {
if moved(table, (offer.at, &feet), &keys).is_none() {
offer.own = table.offered(offer.at).map_or(Own::Copies, Own::Shares);
}
if let Own::Shares(source) = &offer.own {
let stored = offer.stored.clone();
memory.offer(stored, source.clone(), offer.facts);
}
}
}
Offers {
pending,
keys,
range,
}
}
pub(crate) fn whole(&mut self, table: &Table, memory: &Memory) {
let done = |offer: &Offer| {
let value = &table.values[offer.at];
if !matches!(offer.own, Own::Shares(_)) {
return false;
}
let support = value.support();
let mut computed = Segments::default();
value.evaluated.iter().for_each(|e| computed.add(*e));
support.is_bounded() && computed.covers(&Segments::of(support))
};
let (whole, rest): (Vec<Offer>, Vec<Offer>) = std::mem::take(&mut self.pending)
.into_iter()
.partition(done);
self.pending = rest;
self.offer(whole, table, memory);
}
pub(crate) fn rest(&mut self, table: &Table, memory: &Memory) {
let mut rest = std::mem::take(&mut self.pending);
rest.sort_by_key(|offer| matches!(offer.own, Own::Moves));
self.offer(rest, table, memory);
}
fn offer(&mut self, offers: Vec<Offer>, table: &Table, memory: &Memory) {
let moving = offers.iter().any(|offer| matches!(offer.own, Own::Moves));
let feet = if moving { moves(table) } else { HashMap::new() };
for mut offer in offers {
offer.stored.label = table.label(offer.at);
let source = match offer.own {
Own::Shares(source) => source,
Own::Moves => match moved(table, (offer.at, &feet), &self.keys) {
Some(source) => source,
None => continue,
},
Own::Copies => {
let over = self.range.intersect(offer.stored.support);
Offered::Held(vec![Arc::new(table.samples(offer.at, over))])
}
};
let facts = Facts {
settled: true,
..offer.facts
};
memory.offer(offer.stored, source, facts);
}
}
}
fn offerable(
table: &Table,
tys: &Typing,
(id, at): (NodeId, usize),
(path, key): (&str, Hash),
under: &Under,
) -> Option<Stored> {
let value = &table.values[at];
let own = tys.name(id) == path && crate::refs::passes(tys, id).is_none();
let samples = value.pure && !matches!(value.kind, table::Kind::Frames { .. });
if !own || !samples {
return None;
}
let (priced, moved) = under.of_value(at);
let ty = tys.ty(id);
Some(Stored {
key,
identity: crate::refs::identity(tys, id).ok()?,
label: table.label(at),
width: u8::try_from(value.width).expect("a width the typing held"),
codomain: ty.codomain,
rate: ty.rate,
grid: tys.grid(id),
support: value.support(),
priced,
moved,
readable: readable(tys, id) && value.alias().is_none(),
sampled: ty.held == Representation::Sampled,
cuts: Vec::new(),
held: Vec::new(),
})
}
pub(crate) fn readable(tys: &Typing, id: NodeId) -> bool {
tys.ty(id).held == Representation::Sampled && !schedule::anywhere(tys, id)
}
fn moved(
table: &Table,
(at, feet): (usize, &HashMap<usize, (usize, i64)>),
offered: &BTreeMap<usize, Hash>,
) -> Option<Offered> {
let (moved, by) = *feet.get(&at)?;
let resident = match &table.values[moved].kind {
table::Kind::Resident(stored) => Some(stored.key),
_ => None,
};
let of = offered.get(&moved).copied().or(resident)?;
Some(Offered::Moves { of, by })
}
fn moves(table: &Table) -> HashMap<usize, (usize, i64)> {
let mut feet: HashMap<usize, (usize, i64)> = HashMap::new();
for at in table.values.ordered() {
if let Some((read, by)) = table.values[at].moves() {
let foot = feet
.get(&read)
.map_or((read, by), |(foot, more)| (*foot, by + more));
feet.insert(at, foot);
}
}
feet
}
struct Under {
own: Vec<u128>,
apart: Vec<Rc<BTreeSet<usize>>>,
moved: Vec<f64>,
}
impl Under {
fn of(table: &Table) -> Under {
let span = table.values.span();
let mut readers = vec![0u32; span];
let distinct = |at: usize| {
let mut reads = table.values[at].reads.clone();
reads.sort_unstable();
reads.dedup();
reads
};
for at in table.values.ordered() {
for read in distinct(at) {
readers[read] += 1;
}
}
let mut under = Under {
own: vec![0; span],
apart: vec![Rc::default(); span],
moved: vec![0.0; span],
};
for at in table.values.ordered() {
let (mut own, mut moved) = (table.planned[at], table.values[at].moved);
let mut sets: Vec<Rc<BTreeSet<usize>>> = Vec::new();
let mut more = Vec::new();
for read in distinct(at) {
crate::steps::step(1);
moved = moved.max(under.moved[read]);
match readers[read] {
1 => own += under.own[read],
_ => more.push(read),
}
let held = &under.apart[read];
if !held.is_empty() && !sets.iter().any(|set| Rc::ptr_eq(set, held)) {
sets.push(Rc::clone(held));
}
}
under.apart[at] = match (sets.len(), more.is_empty()) {
(0, true) => Rc::default(),
(1, true) => Rc::clone(&sets[0]),
_ => Rc::new(
sets.iter()
.flat_map(|s| s.iter())
.copied()
.chain(more)
.collect(),
),
};
(under.own[at], under.moved[at]) = (own, moved);
}
under
}
fn of_value(&self, at: usize) -> (u128, f64) {
crate::steps::step(self.apart[at].len());
let apart = self.apart[at].iter().map(|s| self.own[*s]).sum::<u128>();
(self.own[at] + apart, self.moved[at])
}
}
#[cfg(test)]
mod tests {
use crate::{RenderConfig, Tier, render};
fn folded(depth: usize) -> u64 {
folded_as(depth, "@P(t - 10ms)")
}
fn folded_as(depth: usize, body: &str) -> u64 {
let mut files = sva_ast::Composition::new();
let decays = "sample(crop(sin(2*pi*440*t)*exp(-t/0.1), 0s, 10s))\n";
files.insert("c0", decays);
for k in 1..=depth {
files.insert(
format!("c{k}"),
body.replace('P', &format!("c{}", k - 1)) + "\n",
);
}
let g = sva_ast::load(&files).expect("a composition");
let before = crate::steps::taken();
let top = format!("c{depth}");
render(&g, &top, RenderConfig::at(8_000), &Tier::default()).expect("a render");
crate::steps::taken() - before
}
#[test]
fn a_chains_offers_and_bounds_fold_steps_linear_in_its_nodes() {
let (short, long) = (folded(100), folded(400));
assert!(short > 0);
assert!(long <= 4 * short, "{short} then {long}");
}
#[test]
fn a_chain_of_scaled_and_summed_reads_folds_steps_linear_in_its_nodes() {
for body in ["0.9*@P(t - 10ms)", "@P(t)*0.5 + @P(t - 10ms)*0.4"] {
let (short, long) = (folded_as(50, body), folded_as(200, body));
assert!(long < 5 * short, "{body}: {short} then {long}");
}
}
}