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