Skip to main content

sva_engine/render/
world.rs

1// Concern: the version a render or stream plays, advanced per edit, and its walk to what memory answers | Non-concern: the value graph | IO: (an edit) -> a version; (a root) -> hits; commit, abort
2
3use std::collections::{BTreeMap, BTreeSet};
4use std::sync::Arc;
5
6use sva_ast::{Expr, Graph, Held};
7use sva_formula::Hash;
8
9use sva_samples::Extent;
10
11use super::terms::{Handle, NOTES, Terms, is_term};
12use super::{RenderConfig, default_end, default_start};
13use crate::cache::{Known as Answer, Stored};
14use crate::error::{Diagnostic, EngineError, Located};
15use crate::instantiate::Instances;
16use crate::query::Representation;
17use crate::schedule;
18use crate::typing::Typing;
19
20pub const STREAMED: &str = "streamed";
21
22pub(super) type Found<'f> = &'f mut dyn FnMut(&str, Hash) -> Answer;
23
24pub(super) struct World {
25    pub(super) graph: Graph,
26    pub(super) instances: Instances,
27    pub(super) typing: Typing,
28    known: BTreeMap<String, Known>,
29    edits: Vec<(String, Option<Held>)>,
30    /// Whether it writes `@notes` as the sum of its terms: a stream's, over a composition that
31    /// defines none.
32    pub(super) notes: bool,
33    replaced: Option<Instances>,
34}
35
36#[derive(Clone)]
37pub(super) struct Known {
38    /// What it computes; none where its identity refuses.
39    identity: Option<Hash>,
40    group: Arc<[String]>,
41    /// A loop's member, a term, a reader of the note sum or one with no identity: never looked up.
42    pinned: bool,
43    reads_notes: bool,
44    looked: bool,
45}
46
47/// The node a version plays: a stream's expression, held as its own node, or a node of the graph.
48pub(super) enum Root<'a> {
49    Streamed(&'a Expr),
50    Node(&'a str),
51}
52
53pub(super) struct Wanted<'a> {
54    pub(super) root: Root<'a>,
55    pub(super) terms: &'a Terms,
56    /// The term it adds, replaces or cuts, as it now stands.
57    pub(super) term: Option<(Handle, &'a Expr)>,
58    /// A graph reaching what it reads, and its reads.
59    pub(super) from: Option<(&'a Graph, Vec<String>)>,
60    /// Whether `from` holds all there is, so a node it lacks is gone.
61    pub(super) whole: bool,
62}
63
64pub(super) struct Advance {
65    adopted: usize,
66    named: Vec<String>,
67    found: BTreeMap<String, Known>,
68    changed: BTreeSet<String>,
69    fresh: BTreeMap<String, BTreeSet<String>>,
70}
71
72pub(super) struct Plan {
73    pub(super) adopted: usize,
74    pub(super) named: usize,
75    pub(super) visited: usize,
76    pub(super) stored: BTreeMap<String, Arc<Stored>>,
77    pub(super) found: BTreeMap<String, Known>,
78}
79
80pub(super) enum Walked {
81    Asks(Vec<Hash>),
82    Planned(Plan),
83}
84
85impl World {
86    /// `graph` as it stands, played at `rate`; `notes` where it writes `@notes` from its terms.
87    pub(super) fn over(graph: &Graph, rate: u32, notes: bool) -> World {
88        World {
89            graph: graph.clone(),
90            instances: Instances::new(rate),
91            typing: Typing::default(),
92            known: BTreeMap::new(),
93            edits: Vec::new(),
94            notes,
95            replaced: None,
96        }
97    }
98
99    /// `target` of `graph`, at `rate`, the version in `world` advanced to it and held: only
100    /// what changed since, and its readers, typed anew. Its root instance; on a refusal the
101    /// version is as it was.
102    pub(super) fn rendered<'w>(
103        world: &'w mut Option<World>,
104        graph: &Graph,
105        target: &str,
106        rate: u32,
107    ) -> Result<(&'w World, String), EngineError> {
108        let world = match world.take() {
109            Some(held) if held.instances.rate() == rate => world.insert(held),
110            _ => world.insert(World::over(graph, rate, false)),
111        };
112        let wanted = Wanted {
113            root: Root::Node(target),
114            terms: &Terms::default(),
115            term: None,
116            from: Some((graph, vec![target.to_string()])),
117            whole: true,
118        };
119        match world.advanced(&wanted) {
120            Ok(advance) => {
121                world.commit(advance.found);
122                let root = world.instances.instance_of(target)?;
123                Ok((world, root))
124            }
125            Err(refused) => {
126                world.abort();
127                Err(refused)
128            }
129        }
130    }
131
132    /// The graph set to what `wanted` plays, what changed named, scanned and typed, each
133    /// instance it may rename named by what it computes, and a walk to what the store answers
134    /// of each changed or newly read. Undone on a refusal or keys to look up.
135    pub(super) fn plan(
136        &mut self,
137        wanted: &Wanted,
138        config: &RenderConfig,
139        found: Found,
140    ) -> Result<Walked, EngineError> {
141        let planned = self
142            .advanced(wanted)
143            .map(|advance| self.walked(advance, config, found));
144        match &planned {
145            Ok(Walked::Planned(_)) => {}
146            _ => {
147                self.abort();
148            }
149        }
150        planned
151    }
152
153    fn advanced(&mut self, wanted: &Wanted) -> Result<Advance, EngineError> {
154        let root = match wanted.root {
155            Root::Streamed(_) => STREAMED,
156            Root::Node(path) => path,
157        };
158        let held: Vec<&String> = self.instances.own_terms.keys().collect();
159        let moved = !held.is_empty() && held != [root];
160        let adopted = match &wanted.from {
161            Some((graph, roots)) => {
162                self.renew(graph, roots, (moved, wanted.whole));
163                self.adopt(graph, roots)
164            }
165            None => 0,
166        };
167        let mut rewritten = Vec::new();
168        if let Root::Streamed(target) = wanted.root {
169            self.set(STREAMED, target, &mut rewritten);
170        }
171        if !wanted.terms.is_empty() && !self.notes {
172            return Err(EngineError::refused(Diagnostic {
173                code: "engine.no_stream".to_string(),
174                message: format!(
175                    "a term is added to `@{NOTES}`, and this composition defines its own `{NOTES}`"
176                ),
177                location: Located::at(NOTES, None),
178                help: format!(
179                    "rename the composition's `{NOTES}`, or play it without adding terms"
180                ),
181            }));
182        }
183        let first = !self.instances.own_terms.contains_key(root);
184        if let Some((handle, term)) = wanted.term {
185            self.set(&handle.node(), term, &mut rewritten);
186        }
187        if self.notes {
188            self.set(NOTES, &wanted.terms.sum(), &mut rewritten);
189        }
190        match first {
191            true => self
192                .instances
193                .hold(&self.graph, &[root.to_string()])
194                .map(|_| ())?,
195            false => self.instances.rewrite(&self.graph, &rewritten)?,
196        }
197        let (named, rescanned) = self.instances.changing();
198        let rescanned = rescanned.map(|(n, before)| (n.to_string(), before.to_vec()));
199        let (mut named, rescanned): (Vec<String>, Vec<(String, Vec<String>)>) =
200            (named.to_vec(), rescanned.collect());
201        if let Some(old) = &self.replaced {
202            let held = |path: &String| old.resolution(path) == self.instances.resolution(path);
203            named.retain(|path| !held(path));
204        }
205        let region = self.region(&named, &rescanned);
206        self.typing.lower(&self.instances, &region)?;
207        if self.notes {
208            wanted.terms.name(&mut self.typing);
209        }
210        let (found, changed) = self.named(&region);
211        let mut fresh: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
212        for name in &named {
213            let deps = self.instances.deps(name).iter().cloned();
214            fresh.insert(name.clone(), deps.collect());
215        }
216        for (name, before) in &rescanned {
217            let deps = self.instances.deps(name).iter();
218            let new = deps.filter(|d| !before.contains(d)).cloned();
219            fresh.insert(name.clone(), new.collect());
220        }
221        Ok(Advance {
222            adopted,
223            named,
224            found,
225            changed,
226            fresh,
227        })
228    }
229
230    fn walked(&self, advance: Advance, config: &RenderConfig, found: Found) -> Walked {
231        let walking = Walking {
232            root: STREAMED,
233            config,
234            whole: false,
235        };
236        let reached = match self.reach(&walking, Some(&advance), found) {
237            Reach::Asks(keys) => return Walked::Asks(keys),
238            Reach::Reached(reached) => reached,
239        };
240        Walked::Planned(Plan {
241            adopted: advance.adopted,
242            named: advance.named.len(),
243            visited: reached.visited.len(),
244            stored: reached.held,
245            found: reached.known,
246        })
247    }
248
249    /// A walk down from `walking`'s root to what memory answers, through every miss: a
250    /// render's whole, or a change's to each node it changed or newly reads.
251    pub(super) fn walk(&self, walking: &Walking, found: Found) -> Reach {
252        self.reach(walking, None, found)
253    }
254
255    fn reach(&self, walking: &Walking, change: Option<&Advance>, found: Found) -> Reach {
256        let walk = Walk {
257            world: self,
258            known: change.map_or_else(BTreeMap::new, |c| c.found.clone()),
259            changed: change.map(|c| &c.changed),
260            fresh: change.map(|c| &c.fresh),
261            pins: Pins::of(&self.instances, walking.config),
262            walking,
263            found,
264        };
265        walk.walked()
266    }
267
268    /// What memory holds `known`'s own value under; none where its identity refuses.
269    fn keyed(&self, path: &str, known: &Known, config: &RenderConfig) -> Option<Hash> {
270        let id = self.typing.id(path)?;
271        let identity = known.identity?;
272        Some(super::value_graph::node_key(
273            &self.typing,
274            id,
275            identity,
276            &config.profile,
277        ))
278    }
279
280    fn set(&mut self, path: &str, expr: &Expr, rewritten: &mut Vec<String>) {
281        if self.graph.expr(path) == Some(expr) {
282            return;
283        }
284        let held = self.graph.defining(expr.clone());
285        let before = self.graph.set(path, Some(held));
286        self.edits.push((path.to_string(), before));
287        rewritten.push(path.to_string());
288    }
289
290    /// Each node played or read anew that `from` holds otherwise than the graph, taken into
291    /// it; where a parse moved, or the root `moved`, the instances are named anew, whole,
292    /// beside the ones set aside.
293    fn renew(&mut self, from: &Graph, roots: &[String], (moved, whole): (bool, bool)) {
294        let read = from.reaching(roots);
295        let played = |p: &str| self.instances.instances_of(p).next().is_some() || read.contains(p);
296        let gone = self.graph.paths().filter(|p| whole && !from.defines(p));
297        let paths = from.paths().filter(|p| self.graph.defines(p)).chain(gone);
298        let held = |p: &&str| self.graph.holds_as(p, from) && self.graph.read_as(p, from);
299        let paths = paths.filter(|p| played(p) && !held(p));
300        let taken: Vec<String> = paths.map(str::to_string).collect();
301        let reparsed = taken.iter().any(|p| !self.graph.holds_as(p, from));
302        for path in taken {
303            let before = self.graph.set(&path, from.held(&path));
304            self.edits.push((path, before));
305        }
306        if !reparsed && !moved {
307            return;
308        }
309        let fresh = Instances::new(self.instances.rate());
310        self.replaced = Some(std::mem::replace(&mut self.instances, fresh));
311    }
312
313    fn adopt(&mut self, from: &Graph, roots: &[String]) -> usize {
314        let taken = self.graph.adopt(from, roots);
315        let count = taken.len();
316        self.edits
317            .extend(taken.into_iter().map(|path| (path, None)));
318        count
319    }
320
321    /// What a change may rename: what it named and scanned again, their old groups and all
322    /// reading them, grouped dependencies first.
323    fn region(&self, named: &[String], rescanned: &[(String, Vec<String>)]) -> Vec<Vec<String>> {
324        let mut region: BTreeSet<String> = named.iter().cloned().collect();
325        for (name, _) in rescanned {
326            region.insert(name.clone());
327            if let Some(known) = self.known.get(name) {
328                region.extend(known.group.iter().cloned());
329            }
330        }
331        let mut up: Vec<String> = region.iter().cloned().collect();
332        while let Some(at) = up.pop() {
333            for reader in self.instances.readers(&at) {
334                if region.insert(reader.to_string()) {
335                    up.push(reader.to_string());
336                }
337            }
338        }
339        let starts: Vec<String> = region.iter().cloned().collect();
340        schedule::grouped(&self.instances, &starts, &|path| region.contains(path))
341    }
342
343    /// Each member of the region named by what its typing computes, and those whose identity
344    /// moved.
345    fn named(&self, region: &[Vec<String>]) -> (BTreeMap<String, Known>, BTreeSet<String>) {
346        let mut found: BTreeMap<String, Known> = BTreeMap::new();
347        let mut changed = BTreeSet::new();
348        for group in region {
349            let looped = schedule::is_loop(&self.instances, group);
350            let reads_notes = self.notes
351                && group.iter().any(|path| {
352                    path == NOTES
353                        || self.instances.deps(path).iter().any(|read| {
354                            let known = found.get(read).or_else(|| self.known.get(read));
355                            read == NOTES || known.is_some_and(|k| k.reads_notes)
356                        })
357                });
358            let members: Arc<[String]> = group.clone().into();
359            for path in group {
360                let typed = self.typing.id(path);
361                let identity = typed.and_then(|id| crate::refs::identity(&self.typing, id).ok());
362                let old = self
363                    .known
364                    .get(path)
365                    .filter(|old| identity.is_some() && old.identity == identity);
366                if old.is_none() {
367                    changed.insert(path.clone());
368                }
369                let pinned =
370                    identity.is_none() || reads_notes || looped || (self.notes && is_term(path));
371                let known = Known {
372                    identity,
373                    group: Arc::clone(&members),
374                    pinned,
375                    reads_notes,
376                    looked: old.is_some_and(|old| old.looked),
377                };
378                found.insert(path.clone(), known);
379            }
380        }
381        (found, changed)
382    }
383
384    /// What nothing reads let go; the typing's freed ids.
385    pub(super) fn commit(&mut self, found: BTreeMap<String, Known>) -> Vec<sva_formula::NodeId> {
386        let mut removed = self.instances.commit();
387        if let Some(old) = self.replaced.take() {
388            let gone = old.paths().filter(|p| !self.instances.holds(p));
389            removed.extend(gone.map(str::to_string));
390        }
391        self.typing.hide(removed.iter().cloned());
392        let freed = self.typing.commit(&self.instances);
393        self.known.extend(found);
394        for path in &removed {
395            self.known.remove(path);
396            if is_term(path) && self.notes {
397                self.graph.set(path, None);
398            }
399        }
400        self.edits.clear();
401        freed
402    }
403
404    /// Everything since the last commit undone; the draft typing's ids.
405    pub(super) fn abort(&mut self) -> Vec<sva_formula::NodeId> {
406        let freed = self.typing.abort();
407        self.instances.abort();
408        if let Some(old) = self.replaced.take() {
409            self.instances = old;
410        }
411        for (path, before) in std::mem::take(&mut self.edits).into_iter().rev() {
412            self.graph.set(&path, before);
413        }
414        freed
415    }
416}
417
418/// How a walk asks memory: from which root, at which rate and profile, for which readings.
419pub(super) struct Walking<'w> {
420    pub(super) root: &'w str,
421    pub(super) config: &'w RenderConfig,
422    /// A render's: every node from the root looked up anew, the root answering over its range.
423    pub(super) whole: bool,
424}
425
426pub(super) struct Reached {
427    pub(super) visited: BTreeSet<String>,
428    pub(super) held: BTreeMap<String, Arc<Stored>>,
429    known: BTreeMap<String, Known>,
430}
431
432pub(super) enum Reach {
433    Asks(Vec<Hash>),
434    Reached(Reached),
435}
436
437/// What the readings asked of a walk pin: every node under a ledger, a node a reading asks
438/// more of than samples, and every node above one a reading asks of, which a hit would hide.
439struct Pins {
440    all: bool,
441    pinned: BTreeSet<String>,
442    asked: BTreeMap<String, Vec<Representation>>,
443}
444
445impl Pins {
446    fn of(inst: &Instances, config: &RenderConfig) -> Pins {
447        let ledger =
448            |ask: &&crate::query::Ask| matches!(ask.representation, Representation::Ledger { .. });
449        let mut pins = Pins {
450            all: config.asks.iter().any(|ask| ledger(&ask)),
451            pinned: BTreeSet::new(),
452            asked: BTreeMap::new(),
453        };
454        for ask in &config.asks {
455            let Ok(path) = inst.instance_of(&ask.node) else {
456                continue;
457            };
458            if !ask.representation.off_samples(true) {
459                pins.pinned.insert(path.clone());
460            }
461            pins.asked.entry(path).or_default().push(ask.representation);
462        }
463        let mut up: Vec<&str> = pins.asked.keys().map(String::as_str).collect();
464        while let Some(at) = up.pop() {
465            for reader in inst.readers(at) {
466                if pins.pinned.insert(reader.to_string()) {
467                    up.push(reader);
468                }
469            }
470        }
471        pins
472    }
473
474    fn holds(&self, path: &str) -> bool {
475        self.all || self.pinned.contains(path)
476    }
477}
478
479struct Walk<'w> {
480    world: &'w World,
481    known: BTreeMap<String, Known>,
482    changed: Option<&'w BTreeSet<String>>,
483    fresh: Option<&'w BTreeMap<String, BTreeSet<String>>>,
484    pins: Pins,
485    walking: &'w Walking<'w>,
486    found: Found<'w>,
487}
488
489struct Visit(String, bool);
490
491impl Walk<'_> {
492    fn known(&self, path: &str) -> Known {
493        let known = self.known.get(path).or_else(|| self.world.known.get(path));
494        known.cloned().expect("an instance named")
495    }
496
497    fn walked(mut self) -> Reach {
498        let (mut visited, mut asks) = (BTreeSet::new(), Vec::new());
499        let mut held: BTreeMap<String, Arc<Stored>> = BTreeMap::new();
500        let (config, whole) = (self.walking.config, self.walking.whole);
501        let mut stack = vec![Visit(self.walking.root.to_string(), false)];
502        while let Some(Visit(path, anew)) = stack.pop() {
503            let mut known = self.known(&path);
504            let pinned = known.pinned || self.pins.holds(&path);
505            let key = self.world.keyed(&path, &known, config).filter(|_| !pinned);
506            if visited.contains(&path) {
507                let asked = anew && !held.contains_key(&path);
508                if let Some(key) = key.filter(|_| asked)
509                    && matches!((self.found)(&path, key), Answer::Unknown)
510                {
511                    asks.push(key);
512                }
513                continue;
514            }
515            let changed = self.changed.is_some_and(|c| c.contains(&path));
516            if !(whole || anew || !known.looked || changed) {
517                continue;
518            }
519            visited.insert(path.clone());
520            let stored = match key {
521                None => None,
522                Some(key) => match (self.found)(&path, key) {
523                    Answer::Hit(hit) => Some((key, hit)),
524                    Answer::Miss => None,
525                    Answer::Unknown => {
526                        asks.push(key);
527                        continue;
528                    }
529                },
530            };
531            let root = whole && path == self.walking.root;
532            let read = self.pins.asked.get(&path).map_or(&[][..], Vec::as_slice);
533            let answering = |hit: &Arc<Stored>| {
534                answers(hit, root, config) && read.iter().all(|r| r.off_samples(hit.sampled))
535            };
536            known.looked = true;
537            match stored.filter(|(_, hit)| answering(hit)) {
538                Some((_, stored)) => {
539                    held.insert(path.clone(), stored);
540                }
541                None => {
542                    let fresh = self.fresh.and_then(|f| f.get(&path));
543                    for read in self.world.instances.deps(&path).iter().rev() {
544                        let anew = fresh.is_some_and(|f| f.contains(read));
545                        stack.push(Visit(read.clone(), anew));
546                    }
547                }
548            }
549            self.known.insert(path, known);
550        }
551        if !asks.is_empty() {
552            asks.sort();
553            asks.dedup();
554            return Reach::Asks(asks);
555        }
556        Reach::Reached(Reached {
557            visited,
558            held,
559            known: self.known,
560        })
561    }
562}
563
564/// A render's root answers where it holds the samples read; any other node, or a stream's
565/// node, where a reader may take its samples.
566fn answers(hit: &Stored, root: bool, config: &RenderConfig) -> bool {
567    let support = hit.support;
568    if !root {
569        return hit.readable;
570    }
571    let start = config.range.start.unwrap_or_else(|| default_start(support));
572    let Some(end) = config.range.end.or(default_end(support)) else {
573        return false;
574    };
575    hit.holds(Extent::new(start, end.max(start)).intersect(support))
576}