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