use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
use sva_ast::{Expr, Graph, Held};
use sva_formula::Hash;
use sva_samples::Extent;
use super::terms::{Handle, NOTES, Terms, is_term};
use super::{RenderConfig, default_end, default_start};
use crate::cache::{Known as Answer, Stored};
use crate::error::{Diagnostic, EngineError, Located};
use crate::instantiate::Instances;
use crate::query::Representation;
use crate::schedule;
use crate::typing::Typing;
pub const STREAMED: &str = "streamed";
pub(super) type Found<'f> = &'f mut dyn FnMut(&str, Hash) -> Answer;
pub(super) struct World {
pub(super) graph: Graph,
pub(super) instances: Instances,
pub(super) typing: Typing,
known: BTreeMap<String, Known>,
edits: Vec<(String, Option<Held>)>,
pub(super) notes: bool,
replaced: Option<Instances>,
}
#[derive(Clone)]
pub(super) struct Known {
identity: Option<Hash>,
group: Arc<[String]>,
pinned: bool,
reads_notes: bool,
looked: bool,
}
pub(super) enum Root<'a> {
Streamed(&'a Expr),
Node(&'a str),
}
pub(super) struct Wanted<'a> {
pub(super) root: Root<'a>,
pub(super) terms: &'a Terms,
pub(super) term: Option<(Handle, &'a Expr)>,
pub(super) from: Option<(&'a Graph, Vec<String>)>,
pub(super) whole: bool,
}
pub(super) struct Advance {
adopted: usize,
named: Vec<String>,
found: BTreeMap<String, Known>,
changed: BTreeSet<String>,
fresh: BTreeMap<String, BTreeSet<String>>,
}
pub(super) struct Plan {
pub(super) adopted: usize,
pub(super) named: usize,
pub(super) visited: usize,
pub(super) stored: BTreeMap<String, Arc<Stored>>,
pub(super) found: BTreeMap<String, Known>,
}
pub(super) enum Walked {
Asks(Vec<Hash>),
Planned(Plan),
}
impl World {
pub(super) fn over(graph: &Graph, rate: u32, notes: bool) -> World {
World {
graph: graph.clone(),
instances: Instances::new(rate),
typing: Typing::default(),
known: BTreeMap::new(),
edits: Vec::new(),
notes,
replaced: None,
}
}
pub(super) fn rendered<'w>(
world: &'w mut Option<World>,
graph: &Graph,
target: &str,
rate: u32,
) -> Result<(&'w World, String), EngineError> {
let world = match world.take() {
Some(held) if held.instances.rate() == rate => world.insert(held),
_ => world.insert(World::over(graph, rate, false)),
};
let wanted = Wanted {
root: Root::Node(target),
terms: &Terms::default(),
term: None,
from: Some((graph, vec![target.to_string()])),
whole: true,
};
match world.advanced(&wanted) {
Ok(advance) => {
world.commit(advance.found);
let root = world.instances.instance_of(target)?;
Ok((world, root))
}
Err(refused) => {
world.abort();
Err(refused)
}
}
}
pub(super) fn plan(
&mut self,
wanted: &Wanted,
config: &RenderConfig,
found: Found,
) -> Result<Walked, EngineError> {
let planned = self
.advanced(wanted)
.map(|advance| self.walked(advance, config, found));
match &planned {
Ok(Walked::Planned(_)) => {}
_ => {
self.abort();
}
}
planned
}
fn advanced(&mut self, wanted: &Wanted) -> Result<Advance, EngineError> {
let root = match wanted.root {
Root::Streamed(_) => STREAMED,
Root::Node(path) => path,
};
let held: Vec<&String> = self.instances.own_terms.keys().collect();
let moved = !held.is_empty() && held != [root];
let adopted = match &wanted.from {
Some((graph, roots)) => {
self.renew(graph, roots, (moved, wanted.whole));
self.adopt(graph, roots)
}
None => 0,
};
let mut rewritten = Vec::new();
if let Root::Streamed(target) = wanted.root {
self.set(STREAMED, target, &mut rewritten);
}
if !wanted.terms.is_empty() && !self.notes {
return Err(EngineError::refused(Diagnostic {
code: "engine.no_stream".to_string(),
message: format!(
"a term is added to `@{NOTES}`, and this composition defines its own `{NOTES}`"
),
location: Located::at(NOTES, None),
help: format!(
"rename the composition's `{NOTES}`, or play it without adding terms"
),
}));
}
let first = !self.instances.own_terms.contains_key(root);
if let Some((handle, term)) = wanted.term {
self.set(&handle.node(), term, &mut rewritten);
}
if self.notes {
self.set(NOTES, &wanted.terms.sum(), &mut rewritten);
}
match first {
true => self
.instances
.hold(&self.graph, &[root.to_string()])
.map(|_| ())?,
false => self.instances.rewrite(&self.graph, &rewritten)?,
}
let (named, rescanned) = self.instances.changing();
let rescanned = rescanned.map(|(n, before)| (n.to_string(), before.to_vec()));
let (mut named, rescanned): (Vec<String>, Vec<(String, Vec<String>)>) =
(named.to_vec(), rescanned.collect());
if let Some(old) = &self.replaced {
let held = |path: &String| old.resolution(path) == self.instances.resolution(path);
named.retain(|path| !held(path));
}
let region = self.region(&named, &rescanned);
self.typing.lower(&self.instances, ®ion)?;
if self.notes {
wanted.terms.name(&mut self.typing);
}
let (found, changed) = self.named(®ion);
let mut fresh: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
for name in &named {
let deps = self.instances.deps(name).iter().cloned();
fresh.insert(name.clone(), deps.collect());
}
for (name, before) in &rescanned {
let deps = self.instances.deps(name).iter();
let new = deps.filter(|d| !before.contains(d)).cloned();
fresh.insert(name.clone(), new.collect());
}
Ok(Advance {
adopted,
named,
found,
changed,
fresh,
})
}
fn walked(&self, advance: Advance, config: &RenderConfig, found: Found) -> Walked {
let walking = Walking {
root: STREAMED,
config,
whole: false,
};
let reached = match self.reach(&walking, Some(&advance), found) {
Reach::Asks(keys) => return Walked::Asks(keys),
Reach::Reached(reached) => reached,
};
Walked::Planned(Plan {
adopted: advance.adopted,
named: advance.named.len(),
visited: reached.visited.len(),
stored: reached.held,
found: reached.known,
})
}
pub(super) fn walk(&self, walking: &Walking, found: Found) -> Reach {
self.reach(walking, None, found)
}
fn reach(&self, walking: &Walking, change: Option<&Advance>, found: Found) -> Reach {
let walk = Walk {
world: self,
known: change.map_or_else(BTreeMap::new, |c| c.found.clone()),
changed: change.map(|c| &c.changed),
fresh: change.map(|c| &c.fresh),
pins: Pins::of(&self.instances, walking.config),
walking,
found,
};
walk.walked()
}
fn keyed(&self, path: &str, known: &Known, config: &RenderConfig) -> Option<Hash> {
let id = self.typing.id(path)?;
let identity = known.identity?;
Some(super::value_graph::node_key(
&self.typing,
id,
identity,
&config.profile,
))
}
fn set(&mut self, path: &str, expr: &Expr, rewritten: &mut Vec<String>) {
if self.graph.expr(path) == Some(expr) {
return;
}
let held = self.graph.defining(expr.clone());
let before = self.graph.set(path, Some(held));
self.edits.push((path.to_string(), before));
rewritten.push(path.to_string());
}
fn renew(&mut self, from: &Graph, roots: &[String], (moved, whole): (bool, bool)) {
let read = from.reaching(roots);
let played = |p: &str| self.instances.instances_of(p).next().is_some() || read.contains(p);
let gone = self.graph.paths().filter(|p| whole && !from.defines(p));
let paths = from.paths().filter(|p| self.graph.defines(p)).chain(gone);
let held = |p: &&str| self.graph.holds_as(p, from) && self.graph.read_as(p, from);
let paths = paths.filter(|p| played(p) && !held(p));
let taken: Vec<String> = paths.map(str::to_string).collect();
let reparsed = taken.iter().any(|p| !self.graph.holds_as(p, from));
for path in taken {
let before = self.graph.set(&path, from.held(&path));
self.edits.push((path, before));
}
if !reparsed && !moved {
return;
}
let fresh = Instances::new(self.instances.rate());
self.replaced = Some(std::mem::replace(&mut self.instances, fresh));
}
fn adopt(&mut self, from: &Graph, roots: &[String]) -> usize {
let taken = self.graph.adopt(from, roots);
let count = taken.len();
self.edits
.extend(taken.into_iter().map(|path| (path, None)));
count
}
fn region(&self, named: &[String], rescanned: &[(String, Vec<String>)]) -> Vec<Vec<String>> {
let mut region: BTreeSet<String> = named.iter().cloned().collect();
for (name, _) in rescanned {
region.insert(name.clone());
if let Some(known) = self.known.get(name) {
region.extend(known.group.iter().cloned());
}
}
let mut up: Vec<String> = region.iter().cloned().collect();
while let Some(at) = up.pop() {
for reader in self.instances.readers(&at) {
if region.insert(reader.to_string()) {
up.push(reader.to_string());
}
}
}
let starts: Vec<String> = region.iter().cloned().collect();
schedule::grouped(&self.instances, &starts, &|path| region.contains(path))
}
fn named(&self, region: &[Vec<String>]) -> (BTreeMap<String, Known>, BTreeSet<String>) {
let mut found: BTreeMap<String, Known> = BTreeMap::new();
let mut changed = BTreeSet::new();
for group in region {
let looped = schedule::is_loop(&self.instances, group);
let reads_notes = self.notes
&& group.iter().any(|path| {
path == NOTES
|| self.instances.deps(path).iter().any(|read| {
let known = found.get(read).or_else(|| self.known.get(read));
read == NOTES || known.is_some_and(|k| k.reads_notes)
})
});
let members: Arc<[String]> = group.clone().into();
for path in group {
let typed = self.typing.id(path);
let identity = typed.and_then(|id| crate::refs::identity(&self.typing, id).ok());
let old = self
.known
.get(path)
.filter(|old| identity.is_some() && old.identity == identity);
if old.is_none() {
changed.insert(path.clone());
}
let pinned =
identity.is_none() || reads_notes || looped || (self.notes && is_term(path));
let known = Known {
identity,
group: Arc::clone(&members),
pinned,
reads_notes,
looked: old.is_some_and(|old| old.looked),
};
found.insert(path.clone(), known);
}
}
(found, changed)
}
pub(super) fn commit(&mut self, found: BTreeMap<String, Known>) -> Vec<sva_formula::NodeId> {
let mut removed = self.instances.commit();
if let Some(old) = self.replaced.take() {
let gone = old.paths().filter(|p| !self.instances.holds(p));
removed.extend(gone.map(str::to_string));
}
self.typing.hide(removed.iter().cloned());
let freed = self.typing.commit(&self.instances);
self.known.extend(found);
for path in &removed {
self.known.remove(path);
if is_term(path) && self.notes {
self.graph.set(path, None);
}
}
self.edits.clear();
freed
}
pub(super) fn abort(&mut self) -> Vec<sva_formula::NodeId> {
let freed = self.typing.abort();
self.instances.abort();
if let Some(old) = self.replaced.take() {
self.instances = old;
}
for (path, before) in std::mem::take(&mut self.edits).into_iter().rev() {
self.graph.set(&path, before);
}
freed
}
}
pub(super) struct Walking<'w> {
pub(super) root: &'w str,
pub(super) config: &'w RenderConfig,
pub(super) whole: bool,
}
pub(super) struct Reached {
pub(super) visited: BTreeSet<String>,
pub(super) held: BTreeMap<String, Arc<Stored>>,
known: BTreeMap<String, Known>,
}
pub(super) enum Reach {
Asks(Vec<Hash>),
Reached(Reached),
}
struct Pins {
all: bool,
pinned: BTreeSet<String>,
asked: BTreeMap<String, Vec<Representation>>,
}
impl Pins {
fn of(inst: &Instances, config: &RenderConfig) -> Pins {
let ledger =
|ask: &&crate::query::Ask| matches!(ask.representation, Representation::Ledger { .. });
let mut pins = Pins {
all: config.asks.iter().any(|ask| ledger(&ask)),
pinned: BTreeSet::new(),
asked: BTreeMap::new(),
};
for ask in &config.asks {
let Ok(path) = inst.instance_of(&ask.node) else {
continue;
};
if !ask.representation.off_samples(true) {
pins.pinned.insert(path.clone());
}
pins.asked.entry(path).or_default().push(ask.representation);
}
let mut up: Vec<&str> = pins.asked.keys().map(String::as_str).collect();
while let Some(at) = up.pop() {
for reader in inst.readers(at) {
if pins.pinned.insert(reader.to_string()) {
up.push(reader);
}
}
}
pins
}
fn holds(&self, path: &str) -> bool {
self.all || self.pinned.contains(path)
}
}
struct Walk<'w> {
world: &'w World,
known: BTreeMap<String, Known>,
changed: Option<&'w BTreeSet<String>>,
fresh: Option<&'w BTreeMap<String, BTreeSet<String>>>,
pins: Pins,
walking: &'w Walking<'w>,
found: Found<'w>,
}
struct Visit(String, bool);
impl Walk<'_> {
fn known(&self, path: &str) -> Known {
let known = self.known.get(path).or_else(|| self.world.known.get(path));
known.cloned().expect("an instance named")
}
fn walked(mut self) -> Reach {
let (mut visited, mut asks) = (BTreeSet::new(), Vec::new());
let mut held: BTreeMap<String, Arc<Stored>> = BTreeMap::new();
let (config, whole) = (self.walking.config, self.walking.whole);
let mut stack = vec![Visit(self.walking.root.to_string(), false)];
while let Some(Visit(path, anew)) = stack.pop() {
let mut known = self.known(&path);
let pinned = known.pinned || self.pins.holds(&path);
let key = self.world.keyed(&path, &known, config).filter(|_| !pinned);
if visited.contains(&path) {
let asked = anew && !held.contains_key(&path);
if let Some(key) = key.filter(|_| asked)
&& matches!((self.found)(&path, key), Answer::Unknown)
{
asks.push(key);
}
continue;
}
let changed = self.changed.is_some_and(|c| c.contains(&path));
if !(whole || anew || !known.looked || changed) {
continue;
}
visited.insert(path.clone());
let stored = match key {
None => None,
Some(key) => match (self.found)(&path, key) {
Answer::Hit(hit) => Some((key, hit)),
Answer::Miss => None,
Answer::Unknown => {
asks.push(key);
continue;
}
},
};
let root = whole && path == self.walking.root;
let read = self.pins.asked.get(&path).map_or(&[][..], Vec::as_slice);
let answering = |hit: &Arc<Stored>| {
answers(hit, root, config) && read.iter().all(|r| r.off_samples(hit.sampled))
};
known.looked = true;
match stored.filter(|(_, hit)| answering(hit)) {
Some((_, stored)) => {
held.insert(path.clone(), stored);
}
None => {
let fresh = self.fresh.and_then(|f| f.get(&path));
for read in self.world.instances.deps(&path).iter().rev() {
let anew = fresh.is_some_and(|f| f.contains(read));
stack.push(Visit(read.clone(), anew));
}
}
}
self.known.insert(path, known);
}
if !asks.is_empty() {
asks.sort();
asks.dedup();
return Reach::Asks(asks);
}
Reach::Reached(Reached {
visited,
held,
known: self.known,
})
}
}
fn answers(hit: &Stored, root: bool, config: &RenderConfig) -> bool {
let support = hit.support;
if !root {
return hit.readable;
}
let start = config.range.start.unwrap_or_else(|| default_start(support));
let Some(end) = config.range.end.or(default_end(support)) else {
return false;
};
hit.holds(Extent::new(start, end.max(start)).intersect(support))
}