1use std::collections::{BTreeMap, BTreeSet};
4
5use crate::{
6 BudgetKind, ContinuationToken, FingerprintValue, IncrementalError, Observation,
7 ObservationKind, Query, QueryBudgets, QueryResult, Revision, ValueFingerprint,
8 state::{Node, RunState},
9};
10
11pub struct IncrementalEngine<K, V> {
13 pub(crate) queries: BTreeMap<K, Query<K, V>>,
14 pub(crate) nodes: BTreeMap<K, Node<K, V>>,
15 pub(crate) reverse: BTreeMap<K, BTreeSet<K>>,
16 pub(crate) source_revisions: BTreeMap<K, Revision>,
17 pub(crate) continuations: BTreeMap<ContinuationToken, K>,
18 pub(crate) next_revision: u64,
19 pub(crate) next_token: u64,
20}
21
22impl<K, V> Default for IncrementalEngine<K, V>
23where
24 K: Ord + Clone,
25{
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl<K, V> IncrementalEngine<K, V>
32where
33 K: Ord + Clone,
34{
35 #[must_use]
37 pub fn new() -> Self {
38 Self {
39 queries: BTreeMap::new(),
40 nodes: BTreeMap::new(),
41 reverse: BTreeMap::new(),
42 source_revisions: BTreeMap::new(),
43 continuations: BTreeMap::new(),
44 next_revision: 1,
45 next_token: 1,
46 }
47 }
48
49 pub fn register_query(&mut self, key: K, query: Query<K, V>) {
51 self.queries.insert(key.clone(), query);
52 self.nodes.entry(key.clone()).or_default().dirty = true;
53 self.mark_dirty_cascade(&key);
54 }
55
56 pub fn register_fn<F>(&mut self, key: K, query: F)
58 where
59 F: for<'a> Fn(&K, &mut QueryFrame<'a, K, V>) -> QueryResult<K, V> + Send + Sync + 'static,
60 {
61 self.register_query(key, Query::new(query));
62 }
63
64 pub fn remove_query(&mut self, key: &K) -> bool {
66 let removed = self.queries.remove(key).is_some();
67 if removed {
68 self.mark_dirty_cascade(key);
69 self.detach_node(key);
70 }
71 removed
72 }
73
74 pub fn invalidate(&mut self, key: &K) -> Revision {
77 let revision = self.alloc_revision();
78 self.source_revisions.insert(key.clone(), revision);
79 self.mark_dirty_cascade(key);
80 revision
81 }
82
83 #[must_use]
85 pub fn source_revision(&self, key: &K) -> Revision {
86 self.source_revisions
87 .get(key)
88 .copied()
89 .unwrap_or(Revision::ZERO)
90 }
91
92 #[must_use]
94 pub fn dirty_keys(&self) -> Vec<K> {
95 self.nodes
96 .iter()
97 .filter(|(_, node)| node.dirty)
98 .map(|(key, _)| key.clone())
99 .collect()
100 }
101
102 #[must_use]
104 pub fn memo_revision(&self, key: &K) -> Option<Revision> {
105 self.nodes.get(key).map(|node| node.revision)
106 }
107
108 #[must_use]
110 pub fn memo_fingerprint(&self, key: &K) -> Option<ValueFingerprint> {
111 self.nodes.get(key).and_then(|node| node.fingerprint)
112 }
113
114 pub(crate) fn alloc_revision(&mut self) -> Revision {
115 let revision = Revision::new(self.next_revision);
116 self.next_revision += 1;
117 revision
118 }
119
120 pub(crate) fn alloc_continuation(&mut self, root: K) -> ContinuationToken {
121 let token = ContinuationToken::new(self.next_token);
122 self.next_token += 1;
123 self.continuations.insert(token, root);
124 token
125 }
126}
127
128impl<K, V> IncrementalEngine<K, V>
129where
130 K: Ord + Clone,
131 V: Clone + Eq + FingerprintValue,
132{
133 pub fn verify(&mut self, key: K) -> QueryResult<K, V> {
135 self.verify_with_budgets(key, QueryBudgets::default())
136 }
137
138 pub fn verify_with_budgets(&mut self, key: K, budgets: QueryBudgets) -> QueryResult<K, V> {
140 let mut run = RunState::new(key.clone(), budgets);
141 self.evaluate(key, &mut run)
142 }
143
144 pub fn resume(&mut self, token: ContinuationToken, budgets: QueryBudgets) -> QueryResult<K, V> {
146 let root = self
147 .continuations
148 .get(&token)
149 .cloned()
150 .ok_or(IncrementalError::UnknownContinuation { token })?;
151 let value = self.verify_with_budgets(root, budgets)?;
152 self.continuations.remove(&token);
153 Ok(value)
154 }
155
156 pub fn verify_many<I>(&mut self, keys: I) -> QueryResult<K, Vec<(K, V)>>
158 where
159 I: IntoIterator<Item = K>,
160 {
161 self.verify_many_with_budgets(keys, QueryBudgets::default())
162 }
163
164 pub fn verify_many_with_budgets<I>(
166 &mut self,
167 keys: I,
168 budgets: QueryBudgets,
169 ) -> QueryResult<K, Vec<(K, V)>>
170 where
171 I: IntoIterator<Item = K>,
172 {
173 let ordered = keys.into_iter().collect::<BTreeSet<_>>();
174 let mut out = Vec::new();
175 for key in ordered {
176 let value = self.verify_with_budgets(key.clone(), budgets)?;
177 out.push((key, value));
178 }
179 Ok(out)
180 }
181
182 pub(crate) fn evaluate(&mut self, key: K, run: &mut RunState<K>) -> QueryResult<K, V> {
183 self.check_cancelled(run)?;
184 if !self.queries.contains_key(&key) {
185 return Err(IncrementalError::UnknownQuery { key });
186 }
187 if let Some(index) = run.stack.iter().position(|item| item == &key) {
188 let mut path = run.stack[index..].to_vec();
189 path.push(key);
190 return Err(IncrementalError::Cycle { path });
191 }
192 self.charge_depth(run, &key)?;
193
194 run.stack.push(key.clone());
195 let result = self.evaluate_pushed(key, run);
196 run.stack.pop();
197 result
198 }
199
200 fn evaluate_pushed(&mut self, key: K, run: &mut RunState<K>) -> QueryResult<K, V> {
201 if self.try_reuse_memo(&key, run)? {
202 let value = self
203 .nodes
204 .get(&key)
205 .and_then(|node| node.value.clone())
206 .ok_or_else(|| IncrementalError::UnknownQuery { key: key.clone() })?;
207 return Ok(value);
208 }
209
210 self.charge_work(run, &key, 1)?;
211 let query = self
212 .queries
213 .get(&key)
214 .cloned()
215 .ok_or_else(|| IncrementalError::UnknownQuery { key: key.clone() })?;
216 let mut observations = Vec::new();
217 let value = {
218 let mut frame = QueryFrame {
219 engine: self,
220 run,
221 observations: &mut observations,
222 };
223 query.run(&key, &mut frame)
224 };
225
226 let value = value?;
227 self.charge_output(run, &key, 1)?;
228 self.commit_value(key.clone(), value, observations);
229 self.nodes
230 .get(&key)
231 .and_then(|node| node.value.clone())
232 .ok_or(IncrementalError::UnknownQuery { key })
233 }
234
235 fn try_reuse_memo(
236 &mut self,
237 key: &K,
238 run: &mut RunState<K>,
239 ) -> Result<bool, IncrementalError<K>> {
240 let Some(node) = self.nodes.get(key) else {
241 return Ok(false);
242 };
243 if node.value.is_none() {
244 return Ok(false);
245 }
246 let dependencies = node.dependencies.clone();
247 let needs_refresh = node.dirty
248 || dependencies
249 .iter()
250 .any(|observation| !self.observation_is_current(observation));
251 if needs_refresh {
252 for observation in dependencies
253 .iter()
254 .filter(|observation| matches!(observation.kind(), ObservationKind::Read))
255 {
256 self.evaluate(observation.key().clone(), run)?;
257 }
258 }
259 if dependencies
260 .iter()
261 .all(|observation| self.observation_is_current(observation))
262 {
263 if let Some(node) = self.nodes.get_mut(key) {
264 node.dirty = false;
265 }
266 Ok(true)
267 } else {
268 Ok(false)
269 }
270 }
271
272 fn commit_value(&mut self, key: K, value: V, dependencies: Vec<Observation<K>>) {
273 let fingerprint = value.incremental_fingerprint();
274 let old_dependencies = self
275 .nodes
276 .get(&key)
277 .map(|node| node.dependencies.clone())
278 .unwrap_or_default();
279 for observation in old_dependencies {
280 if let Some(dependents) = self.reverse.get_mut(observation.key()) {
281 dependents.remove(&key);
282 }
283 }
284
285 let same_value = self
289 .nodes
290 .get(&key)
291 .and_then(|node| node.value.as_ref())
292 .is_some_and(|old| old == &value);
293 let revision = if same_value {
294 self.nodes
295 .get(&key)
296 .map(|node| node.revision)
297 .unwrap_or_else(|| self.alloc_revision())
298 } else {
299 self.alloc_revision()
300 };
301
302 for observation in &dependencies {
303 self.reverse
304 .entry(observation.key().clone())
305 .or_default()
306 .insert(key.clone());
307 }
308 self.nodes.insert(
309 key,
310 Node {
311 revision,
312 dirty: false,
313 value: Some(value),
314 fingerprint: Some(fingerprint),
315 dependencies,
316 },
317 );
318 }
319
320 fn observation_is_current(&self, observation: &Observation<K>) -> bool {
321 match observation.kind() {
322 ObservationKind::Read => self.nodes.get(observation.key()).is_some_and(|node| {
323 !node.dirty
324 && node.revision == observation.revision()
325 && node.fingerprint == observation.fingerprint()
326 }),
327 ObservationKind::Missing
328 | ObservationKind::Listing
329 | ObservationKind::Policy
330 | ObservationKind::Epoch
331 | ObservationKind::Custom(_) => {
332 self.source_revision(observation.key()) == observation.revision()
333 }
334 }
335 }
336
337 pub(crate) fn memo_observation(&self, key: &K) -> Result<Observation<K>, IncrementalError<K>> {
338 let node = self
339 .nodes
340 .get(key)
341 .ok_or_else(|| IncrementalError::UnknownQuery { key: key.clone() })?;
342 let fingerprint = node
343 .fingerprint
344 .ok_or_else(|| IncrementalError::UnknownQuery { key: key.clone() })?;
345 Ok(Observation::read(key.clone(), node.revision, fingerprint))
346 }
347
348 pub(crate) fn record_observation(
349 &mut self,
350 run: &mut RunState<K>,
351 observations: &mut Vec<Observation<K>>,
352 observation: Observation<K>,
353 ) -> Result<(), IncrementalError<K>> {
354 self.charge_observation(run, observation.key())?;
355 observations.push(observation);
356 Ok(())
357 }
358
359 pub(crate) fn charge_work(
360 &mut self,
361 run: &mut RunState<K>,
362 key: &K,
363 units: usize,
364 ) -> Result<(), IncrementalError<K>> {
365 self.check_cancelled(run)?;
366 if run.work.saturating_add(units) > run.budgets.max_work {
367 return Err(self.budget_error(
368 run,
369 key,
370 BudgetKind::Work,
371 run.budgets.max_work,
372 run.work.saturating_add(units),
373 ));
374 }
375 run.work += units;
376 Ok(())
377 }
378
379 pub(crate) fn charge_output(
380 &mut self,
381 run: &mut RunState<K>,
382 key: &K,
383 units: usize,
384 ) -> Result<(), IncrementalError<K>> {
385 self.check_cancelled(run)?;
386 if run.output.saturating_add(units) > run.budgets.max_output {
387 return Err(self.budget_error(
388 run,
389 key,
390 BudgetKind::Output,
391 run.budgets.max_output,
392 run.output.saturating_add(units),
393 ));
394 }
395 run.output += units;
396 Ok(())
397 }
398
399 fn charge_depth(&mut self, run: &mut RunState<K>, key: &K) -> Result<(), IncrementalError<K>> {
400 if run.stack.len().saturating_add(1) > run.budgets.max_depth {
401 return Err(self.budget_error(
402 run,
403 key,
404 BudgetKind::Depth,
405 run.budgets.max_depth,
406 run.stack.len().saturating_add(1),
407 ));
408 }
409 Ok(())
410 }
411
412 fn charge_observation(
413 &mut self,
414 run: &mut RunState<K>,
415 key: &K,
416 ) -> Result<(), IncrementalError<K>> {
417 if run.observations.saturating_add(1) > run.budgets.max_observations {
418 return Err(self.budget_error(
419 run,
420 key,
421 BudgetKind::Observations,
422 run.budgets.max_observations,
423 run.observations.saturating_add(1),
424 ));
425 }
426 run.observations += 1;
427 Ok(())
428 }
429
430 fn check_cancelled(&self, run: &RunState<K>) -> Result<(), IncrementalError<K>> {
431 if run.cancelled {
432 Err(IncrementalError::Cancelled)
433 } else {
434 Ok(())
435 }
436 }
437
438 fn budget_error(
439 &mut self,
440 run: &RunState<K>,
441 _key: &K,
442 kind: BudgetKind,
443 limit: usize,
444 consumed: usize,
445 ) -> IncrementalError<K> {
446 let continuation = Some(self.alloc_continuation(run.root.clone()));
447 IncrementalError::BudgetExceeded {
448 kind,
449 limit,
450 consumed,
451 continuation,
452 }
453 }
454}
455
456impl<K, V> IncrementalEngine<K, V>
457where
458 K: Ord + Clone,
459{
460 fn mark_dirty_cascade(&mut self, key: &K) {
461 let mut pending = BTreeSet::from([key.clone()]);
462 let mut seen = BTreeSet::new();
463 while let Some(next) = pending.iter().next().cloned() {
464 pending.remove(&next);
465 if !seen.insert(next.clone()) {
466 continue;
467 }
468 if let Some(node) = self.nodes.get_mut(&next) {
469 node.dirty = true;
470 }
471 if let Some(dependents) = self.reverse.get(&next) {
472 pending.extend(dependents.iter().cloned());
473 }
474 }
475 }
476
477 fn detach_node(&mut self, key: &K) {
478 let Some(node) = self.nodes.remove(key) else {
479 return;
480 };
481 for observation in node.dependencies {
482 if let Some(dependents) = self.reverse.get_mut(observation.key()) {
483 dependents.remove(key);
484 }
485 }
486 self.reverse.remove(key);
487 }
488}
489
490pub struct QueryFrame<'a, K, V> {
492 pub(crate) engine: &'a mut IncrementalEngine<K, V>,
493 pub(crate) run: &'a mut RunState<K>,
494 pub(crate) observations: &'a mut Vec<Observation<K>>,
495}