Skip to main content

sva_engine/render/
run.rs

1// Concern: renders or warms a target over memory, from the root down to what it answers | Non-concern: what memory keeps or writes, computing a value | IO: (&Graph, target, Tier) -> Render, CacheStats
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use sva_ast::Graph;
6use sva_formula::{Hash, NodeId};
7
8use super::offer::{Offers, readable};
9use super::table::{self, Table};
10use super::{Render, RenderConfig, closed, driving, drove, frontier, planned_over};
11use crate::cache::{Backend, CacheStats, Recording, Tier};
12use crate::error::EngineError;
13use crate::instantiate;
14use crate::schedule;
15use crate::typing;
16
17/// `target` over `tier`, from the root down: a node memory answers stands as its samples, and
18/// nothing under it is typed, planned or looked up. What the rest computes memory keeps as it
19/// says, offered as nodes where a reader may take them; only `persist` commits them to a disk.
20/// A failing disk fails no render: memory writes nothing more to it, and the stats say why.
21pub async fn render_over<B: Backend>(
22    graph: &Graph,
23    target: &str,
24    config: RenderConfig,
25    tier: &Tier<B>,
26) -> Result<Render, EngineError> {
27    match run(graph, target, config, tier, Keep::Wanted).await? {
28        Reached::Render(held) => Ok(*held),
29        Reached::Held(_) => unreachable!("a render reads a held root's samples"),
30    }
31}
32
33/// `render_over`'s work alone, `config` asking nothing: a root memory answers ends it unread.
34pub async fn warm<B: Backend>(
35    graph: &Graph,
36    target: &str,
37    config: RenderConfig,
38    tier: &Tier<B>,
39) -> Result<CacheStats, EngineError> {
40    Ok(
41        match run(graph, target, config, tier, Keep::Nothing).await? {
42            Reached::Render(mut held) => held.cache_stats.take().expect("a render reports"),
43            Reached::Held(stats) => stats,
44        },
45    )
46}
47
48#[derive(Clone, Copy, PartialEq, Eq)]
49enum Keep {
50    Wanted,
51    Nothing,
52}
53
54enum Reached {
55    Render(Box<Render>),
56    Held(CacheStats),
57}
58
59async fn run<B: Backend>(
60    graph: &Graph,
61    target: &str,
62    config: RenderConfig,
63    tier: &Tier<B>,
64    keep: Keep,
65) -> Result<Reached, EngineError> {
66    let round = tier.begin();
67    let mut recording = Recording::over(tier.memory());
68    let instances = instantiate::instantiate(graph, target, config.rate)?;
69    let root = instances.instance_of(target)?;
70    let order = schedule::schedule_from(&instances, std::slice::from_ref(&root))?;
71    let keys = keys(graph, &instances, &order, &config);
72    let mut found = frontier::Frontier::from((&instances, &order), &keys, &root, &config);
73    found.walked(tier, round).await;
74    if keep == Keep::Nothing && found.stored.contains_key(&root) {
75        recording.found(found.lookups);
76        return Ok(Reached::Held(recording.stats()));
77    }
78    let (mut held, typed) = loop {
79        found.walked(tier, round).await;
80        let tys = typing::infer_over(&instances, &order.within(&found.visited), &found.stored)?;
81        let id = tys
82            .id(&root)
83            .ok_or_else(|| EngineError::UnknownNode(root.clone()))?;
84        let bounds: BTreeSet<NodeId> = keys
85            .keys()
86            .filter_map(|path| tys.id(path))
87            .filter(|id| readable(&tys, *id))
88            .collect();
89        let typed = tys.lowered().to_vec();
90        let mut held = planned_over(&instances, (tys, id), config.clone(), &bounds)?;
91        let short = match (&mut held.table, held.range) {
92            (Some(table), Some(range)) => {
93                let mut needs = table.needs(range);
94                while !needs.is_empty() {
95                    let fetched = tier.fetch(&needs).await;
96                    for (key, parts) in fetched.handed {
97                        table.took(key, &parts);
98                    }
99                    needs = fetched.left;
100                }
101                short(table, range)
102            }
103            _ => Vec::new(),
104        };
105        if short.is_empty() {
106            break (held, typed);
107        }
108        for path in short {
109            found.reopen(&path);
110        }
111    };
112    let mut offers = Offers::of(&mut held, (&keys, &found), tier.memory());
113    recording.found(std::mem::take(&mut found.lookups));
114    let walked = recording.stats();
115    match driving(&mut held, recording)? {
116        Some(mut driver) => {
117            while driver.pull()? {
118                offers.whole(&driver.table, tier.memory());
119                let needs = driver.table.needs(driver.next());
120                for (key, parts) in tier.fetch(&needs).await.handed {
121                    driver.table.took(key, &parts);
122                }
123            }
124            offers.rest(&driver.table, tier.memory());
125            drove(&mut held, driver, keep == Keep::Wanted);
126        }
127        None => held.cache_stats = Some(walked),
128    }
129    let computed = held.table.as_ref().map_or(Vec::new(), |table| {
130        let computed = table.values.iter();
131        let computed = computed.filter(|(_, v)| !matches!(v.kind, table::Kind::Resident { .. }));
132        computed.map(|(_, v)| v.name.clone()).collect()
133    });
134    if let Some(stats) = &mut held.cache_stats {
135        stats.typed = typed;
136        stats.planned = computed;
137    }
138    closed(&mut held)?;
139    Ok(Reached::Render(Box::new(held)))
140}
141
142/// Each instance's node key: its source identity at the render's rate and profile.
143pub(crate) fn keys(
144    graph: &Graph,
145    instances: &instantiate::Instances,
146    order: &schedule::Order,
147    config: &RenderConfig,
148) -> BTreeMap<String, Hash> {
149    crate::source::identities(graph, instances, order)
150        .into_iter()
151        .map(|(path, identity)| {
152            let key = crate::cache::node_key(identity, config.rate, &config.profile);
153            (path, key)
154        })
155        .collect()
156}
157
158/// Each node memory answered whose samples miss some its readers ask over `range`.
159fn short(table: &Table, range: sva_samples::Extent) -> Vec<String> {
160    let needs = table.demand(range);
161    table
162        .values
163        .iter()
164        .filter(|(at, value)| {
165            matches!(value.kind, table::Kind::Resident { .. }) && !needs[*at].compute.is_empty()
166        })
167        .map(|(_, value)| value.name.clone())
168        .collect()
169}