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