Skip to main content

sva_engine/render/
run.rs

1// Concern: renders 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
2
3use std::collections::BTreeMap;
4use std::pin::Pin;
5use std::sync::Arc;
6
7use sva_ast::Graph;
8use sva_formula::NodeId;
9use sva_samples::Buffer;
10
11use super::value_graph;
12use super::world::{Reach, Reached, Walking, World};
13use super::{Render, RenderConfig, closed, driving, dropped, drove, ended, planned_over};
14use crate::cache::{Backend, Recording, Stored, Tier};
15use crate::error::EngineError;
16use crate::schedule;
17
18/// Whether a render's caller let it go, asked between blocks.
19pub trait Abandon {
20    fn abandoned(&self) -> Pin<Box<dyn Future<Output = bool> + '_>>;
21}
22
23pub struct Never;
24
25/// What hears the root's samples block by block as they are pulled.
26pub trait Hear {
27    /// False lets the render go.
28    fn heard(&self, block: &Buffer) -> bool;
29}
30
31/// Renders made in turn, each advancing the version the last held.
32#[derive(Default)]
33pub struct Session {
34    own: Option<World>,
35    /// The composition with every volatile parameter at its stand-in.
36    pub(super) stand_in: Option<World>,
37}
38
39impl Abandon for Never {
40    fn abandoned(&self) -> Pin<Box<dyn Future<Output = bool> + '_>> {
41        Box::pin(std::future::ready(false))
42    }
43}
44
45/// `target` over `tier`, from the root down, in a session of its own.
46pub async fn render_over<B: Backend>(
47    graph: &Graph,
48    target: &str,
49    config: RenderConfig,
50    tier: &Tier<B>,
51) -> Result<Render, EngineError> {
52    render_in(
53        &mut Session::default(),
54        graph,
55        target,
56        config,
57        tier,
58        (&Never, None),
59    )
60    .await
61}
62
63/// `target` over `tier`, from the root down, typed anew only where `session` typed it
64/// otherwise; a node memory answers stands as its samples. With `out` dropped and no reading,
65/// a root it answers ends the render unread. Abandoned, or a block not heard, it stops before
66/// its next block.
67pub async fn render_in<B: Backend>(
68    session: &mut Session,
69    graph: &Graph,
70    target: &str,
71    config: RenderConfig,
72    tier: &Tier<B>,
73    (abandon, hear): (&dyn Abandon, Option<&dyn Hear>),
74) -> Result<Render, EngineError> {
75    let memory = tier.memory();
76    let mut recording = Recording::over(memory);
77    let Session { own, stand_in } = session;
78    let (world, root) = World::rendered(own, graph, target, config.rate)?;
79    let (instances, typed) = (&world.instances, &world.typing);
80    let id = typed
81        .id(&root)
82        .ok_or_else(|| EngineError::UnknownNode(root.clone()))?;
83    let (config, decided) = ended(typed, id, config);
84    let lowered = typed.lowered().to_vec();
85    let round = tier.begin();
86    let found = walked(world, (&root, &config), (tier, round, &mut recording)).await;
87    let tys = typed.clone();
88    if dropped(&config) && found.held.contains_key(&root) {
89        let schedule = schedule::plan(&tys, id, &config.asks);
90        let mut held = Render::shell((tys, id), (config, schedule), memory.clone());
91        let mut stats = recording.stats(memory);
92        stats.typed = lowered;
93        held.cache_stats = Some(stats);
94        return Ok(held);
95    }
96    let hits: BTreeMap<NodeId, Arc<Stored>> = found
97        .held
98        .iter()
99        .filter_map(|(path, stored)| Some((tys.id(path)?, Arc::clone(stored))))
100        .collect();
101    let mut held = planned_over(
102        (graph, target, instances),
103        (tys, id),
104        (config, &mut *stand_in, memory.clone()),
105        (decided, &hits),
106    )?;
107    let mut retyped = lowered;
108    retyped.append(&mut held.stand_in_typed);
109    if let (Some(value_graph), Some(range)) = (&mut held.value_graph, held.range) {
110        loop {
111            let mut needs = value_graph.needs(range);
112            while !needs.is_empty() {
113                let fetched = tier.fetch(&needs).await;
114                for (key, parts) in fetched.handed {
115                    value_graph.took(key, &parts);
116                }
117                needs = fetched.left;
118            }
119            let short = value_graph.short((value_graph.root, range, value_graph::Past::Held));
120            if short.is_empty() {
121                break;
122            }
123            for at in short {
124                value_graph.read_on(&held.tys, at)?;
125            }
126            value_graph.settled(value_graph.root, &[], (range.start, false));
127            value_graph.refuse_endless(range)?;
128        }
129        value_graph.offers(&held.tys, range);
130    }
131    let walked = recording.stats(memory);
132    match driving(&mut held, (memory, recording))? {
133        Some(mut driver) => {
134            loop {
135                if abandon.abandoned().await {
136                    return Err(EngineError::Abandoned);
137                }
138                let from = driver.at;
139                if !driver.pull()? {
140                    break;
141                }
142                if hear.is_some_and(|hear| !hear.heard(&driver.played(from))) {
143                    return Err(EngineError::Abandoned);
144                }
145                let needs = driver.value_graph.needs(driver.next());
146                for (key, parts) in tier.fetch(&needs).await.handed {
147                    driver.value_graph.took(key, &parts);
148                }
149            }
150            drove(&mut held, driver);
151        }
152        None => held.cache_stats = Some(walked),
153    }
154    let computed = held.value_graph.as_ref().map_or(Vec::new(), |value_graph| {
155        let computed = value_graph.values.iter();
156        let computed =
157            computed.filter(|(_, v)| !matches!(v.kind, value_graph::Kind::Resident { .. }));
158        computed.map(|(_, v)| v.name.clone()).collect()
159    });
160    if let Some(stats) = &mut held.cache_stats {
161        stats.typed = retyped;
162        stats.planned = computed;
163        stats.unslotted = held.unslotted.clone();
164    }
165    closed(&mut held)?;
166    Ok(held)
167}
168
169/// What memory answers from `root` down, each key it cannot answer looked up this round.
170async fn walked<B: Backend>(
171    world: &World,
172    (root, config): (&str, &RenderConfig),
173    (tier, round, seen): (&Tier<B>, u64, &mut Recording),
174) -> Reached {
175    let walking = Walking {
176        root,
177        config,
178        whole: true,
179    };
180    loop {
181        let memory = tier.memory();
182        match world.walk(&walking, &mut |node, key| {
183            memory.answered((key, round), (node, seen))
184        }) {
185            Reach::Reached(reached) => return reached,
186            Reach::Asks(keys) => {
187                for key in keys {
188                    tier.lookup(key, round).await;
189                }
190            }
191        }
192    }
193}