1use super::attempt::{intersect_limits, parse_absolute_path};
2use super::*;
3
4impl ExprTreeCalc {
5 pub fn set_tree_codec_policy(&mut self, patch: CodecPolicyPatch) {
7 let mut state = self.state.write().expect("calc state poisoned");
8 state.tree_codec_policy = patch;
9 bump_generation(&mut state.control_generation);
10 }
11
12 pub fn set_dir_codec_policy(&mut self, directory: TablePath, patch: CodecPolicyPatch) {
14 let key = path_key(&directory);
15 let mut state = self.state.write().expect("calc state poisoned");
16 state.dir_codec_policies.insert(key, patch);
17 bump_generation(&mut state.control_generation);
18 }
19
20 pub fn set_cell_codec_policy(&mut self, cell: TablePath, patch: CodecPolicyPatch) {
22 let key = path_key(&cell);
23 let mut state = self.state.write().expect("calc state poisoned");
24 state.cell_codec_policies.insert(key, patch);
25 bump_generation(&mut state.control_generation);
26 }
27
28 #[must_use]
31 pub fn effective_codec_policy(&self, cell: &TablePath) -> EffectiveCodecPolicy {
32 let state = self.state.read().expect("calc state poisoned");
33 effective_codec_policy(
34 &state.tree_codec_policy,
35 &state.dir_codec_policies,
36 &state.cell_codec_policies,
37 &path_key(cell),
38 )
39 }
40
41 pub fn set_tree_calc_policy(&mut self, patch: CalcPolicyPatch) {
43 {
44 let mut state = self.state.write().expect("calc state poisoned");
45 state.tree_calc_policy = patch;
46 bump_generation(&mut state.control_generation);
47 }
48 self.invalidate_calc_policy_matching(|_| true);
49 self.schedule_dirty_automatic();
50 }
51
52 pub fn set_dir_calc_policy(&mut self, directory: TablePath, patch: CalcPolicyPatch) {
54 let key = path_key(&directory);
55 {
56 let mut state = self.state.write().expect("calc state poisoned");
57 state.dir_calc_policies.insert(key, patch);
58 bump_generation(&mut state.control_generation);
59 }
60 self.invalidate_calc_policy_matching(|cell| is_descendant_or_same(&directory, cell));
61 self.schedule_dirty_automatic();
62 }
63
64 pub fn set_cell_calc_policy(&mut self, cell: TablePath, patch: CalcPolicyPatch) {
66 let key = path_key(&cell);
67 {
68 let mut state = self.state.write().expect("calc state poisoned");
69 state.cell_calc_policies.insert(key.clone(), patch);
70 bump_generation(&mut state.control_generation);
71 }
72 self.engine
73 .invalidate(&CalcQuery::EffectivePolicy(key.clone()));
74 self.emit_change("calculation-policy", &key);
75 self.schedule_dirty_automatic();
76 }
77
78 #[must_use]
80 pub fn effective_calc_policy(&self, cell: &TablePath) -> EffectiveCalcPolicy {
81 let state = self.state.read().expect("calc state poisoned");
82 effective_calc_policy(
83 &state.tree_calc_policy,
84 &state.dir_calc_policies,
85 &state.cell_calc_policies,
86 &path_key(cell),
87 )
88 }
89
90 pub fn set_tree_authority_policy(&mut self, patch: AuthorityPolicyPatch) {
92 {
93 let mut state = self.state.write().expect("calc state poisoned");
94 state.tree_authority_policy = patch;
95 bump_generation(&mut state.control_generation);
96 }
97 self.invalidate_authority_policy_matching(|_| true);
98 self.schedule_dirty_automatic();
99 }
100
101 pub fn set_dir_authority_policy(&mut self, directory: TablePath, patch: AuthorityPolicyPatch) {
103 let key = path_key(&directory);
104 {
105 let mut state = self.state.write().expect("calc state poisoned");
106 state.dir_authority_policies.insert(key, patch);
107 bump_generation(&mut state.control_generation);
108 }
109 self.invalidate_authority_policy_matching(|cell| is_descendant_or_same(&directory, cell));
110 self.schedule_dirty_automatic();
111 }
112
113 pub fn set_cell_authority_policy(&mut self, cell: TablePath, patch: AuthorityPolicyPatch) {
115 let key = path_key(&cell);
116 {
117 let mut state = self.state.write().expect("calc state poisoned");
118 state.cell_authority_policies.insert(key.clone(), patch);
119 bump_generation(&mut state.control_generation);
120 }
121 self.engine
122 .invalidate(&CalcQuery::AuthorityPolicy(key.clone()));
123 self.emit_change("authority-policy", &key);
124 self.schedule_dirty_automatic();
125 }
126
127 #[must_use]
129 pub fn effective_authority(&self, cell: &TablePath) -> EffectiveAuthority {
130 let state = self.state.read().expect("calc state poisoned");
131 effective_authority(
132 &state.authority_ceiling,
133 &state.tree_authority_policy,
134 &state.dir_authority_policies,
135 &state.cell_authority_policies,
136 &path_key(cell),
137 )
138 }
139
140 pub fn set_codec_registry_revision(&mut self, revision: u64) {
142 {
143 let mut state = self.state.write().expect("calc state poisoned");
144 state.codec_registry_revision = revision;
145 bump_generation(&mut state.control_generation);
146 }
147 self.engine.invalidate(&CalcQuery::CodecRegistry);
148 }
149
150 pub fn mount(
152 &mut self,
153 path: TablePath,
154 resource: MountResource,
155 backend: BackendKind,
156 epoch: MountEpoch,
157 ) {
158 let key = path_key(&path);
159 {
160 let mut state = self.state.write().expect("calc state poisoned");
161 state.mounts.insert(
162 key.clone(),
163 MountState {
164 resource,
165 backend,
166 epoch,
167 },
168 );
169 bump_generation(&mut state.control_generation);
170 }
171 self.refresh_samples
172 .insert(key.clone(), BackendRefreshSample::new(epoch));
173 self.engine.invalidate(&CalcQuery::MountEpoch(key));
174 }
175
176 pub fn unmount(&mut self, path: &TablePath) -> bool {
178 let key = path_key(path);
179 let removed = {
180 let mut state = self.state.write().expect("calc state poisoned");
181 let removed = state.mounts.remove(&key).is_some();
182 if removed {
183 bump_generation(&mut state.control_generation);
184 }
185 removed
186 };
187 self.refresh_sources.remove(&key);
188 self.refresh_samples.remove(&key);
189 if removed {
190 self.engine.invalidate(&CalcQuery::MountEpoch(key));
191 }
192 removed
193 }
194
195 pub fn observe_mount_epoch(&mut self, path: &TablePath, epoch: MountEpoch) {
197 let key = path_key(path);
198 {
199 let mut state = self.state.write().expect("calc state poisoned");
200 if let Some(mount) = state.mounts.get_mut(&key) {
201 mount.epoch = epoch;
202 bump_generation(&mut state.control_generation);
203 }
204 }
205 self.refresh_samples
206 .entry(key.clone())
207 .and_modify(|sample| sample.epoch = epoch)
208 .or_insert_with(|| BackendRefreshSample::new(epoch));
209 self.engine.invalidate(&CalcQuery::MountEpoch(key));
210 }
211
212 pub fn request_cancellation(&self) {
214 self.cancel_requested.store(true, Ordering::Release);
215 }
216
217 pub fn verify_cell(&mut self, path: &TablePath) -> Result<Value, CalcError> {
219 self.verify_cell_with_limits(path, CalcLimits::default())
220 }
221
222 pub fn verify_cell_with_limits(
224 &mut self,
225 path: &TablePath,
226 limits: CalcLimits,
227 ) -> Result<Value, CalcError> {
228 let mut report = self.calculate_cells([path.clone()], CalcRequestMode::Verify, limits);
229 report
230 .cells
231 .pop()
232 .map(|cell| cell.result)
233 .unwrap_or_else(|| {
234 Err(CalcError::NotCalculated {
235 path: path_key(path),
236 })
237 })
238 }
239
240 pub fn recalculate_cell(&mut self, path: &TablePath) -> Result<Value, CalcError> {
242 self.recalculate_cell_with_limits(path, CalcLimits::default())
243 }
244
245 pub fn recalculate_cell_with_limits(
247 &mut self,
248 path: &TablePath,
249 limits: CalcLimits,
250 ) -> Result<Value, CalcError> {
251 let mut report = self.calculate_cells([path.clone()], CalcRequestMode::ForceRoots, limits);
252 report
253 .cells
254 .pop()
255 .map(|cell| cell.result)
256 .unwrap_or_else(|| {
257 Err(CalcError::NotCalculated {
258 path: path_key(path),
259 })
260 })
261 }
262
263 pub fn recalculate_recursive(&mut self, path: &TablePath) -> Result<Value, CalcError> {
265 let mut report = self.calculate_cells(
266 [path.clone()],
267 CalcRequestMode::ForceRecursive,
268 CalcLimits::default(),
269 );
270 report
271 .cells
272 .pop()
273 .map(|cell| cell.result)
274 .unwrap_or_else(|| {
275 Err(CalcError::NotCalculated {
276 path: path_key(path),
277 })
278 })
279 }
280
281 pub fn calculate_cells(
283 &mut self,
284 roots: impl IntoIterator<Item = TablePath>,
285 mode: CalcRequestMode,
286 limits: CalcLimits,
287 ) -> DirectedCalcReport {
288 let roots = roots
289 .into_iter()
290 .map(|path| path_key(&path))
291 .collect::<BTreeSet<_>>();
292 let request_id = self.allocate_request_id();
293 let directed_cells = match mode {
294 CalcRequestMode::ForceRecursive => self.force_recursive_closure(&roots),
295 CalcRequestMode::Verify | CalcRequestMode::ForceRoots => roots.clone(),
296 };
297 match mode {
298 CalcRequestMode::Verify => {}
299 CalcRequestMode::ForceRoots => {
300 for root in &roots {
301 self.engine.invalidate(&CalcQuery::ForceEpoch(root.clone()));
302 }
303 }
304 CalcRequestMode::ForceRecursive => {
305 for cell in &directed_cells {
306 self.engine.invalidate(&CalcQuery::ForceEpoch(cell.clone()));
307 }
308 }
309 }
310
311 let request = ActiveRequest {
312 id: request_id,
313 reason: CalcReason::for_mode(mode),
314 directed_cells,
315 automatic: false,
316 };
317 let mut cells = Vec::new();
318 for root in roots {
319 let effective = self.effective_calc_policy(&parse_absolute_path(&root));
320 let root_limits = intersect_limits(limits, effective.budget);
321 let result = self.execute_root(root.clone(), request.clone(), root_limits, None);
322 let failed = result.is_err();
323 cells.push(DirectedCellResult { cell: root, result });
324 if failed && effective.error_mode == ErrorMode::FailFast {
325 break;
326 }
327 }
328 DirectedCalcReport { request_id, cells }
329 }
330
331 pub fn watch(&mut self, buffer_policy: BufferPolicy) -> CalcWatch {
333 let watch = CalcWatch::new(self.next_watch_id, buffer_policy);
334 self.next_watch_id = self.next_watch_id.saturating_add(1);
335 self.watches.push(watch.clone());
336 watch
337 }
338
339 pub fn cancel_request(&mut self, request_id: RequestId) -> bool {
341 let key = self
342 .automatic_queue
343 .iter()
344 .find_map(|(cell, queued)| (queued.request_id == request_id).then(|| cell.clone()));
345 let Some(key) = key else {
346 return false;
347 };
348 self.automatic_queue.remove(&key);
349 self.bump_queue_generation();
350 self.emit_progress("cancelled", &key, request_id);
351 true
352 }
353
354 pub fn current_cell(&self, path: &TablePath) -> Result<Value, CalcError> {
358 let key = path_key(path);
359 self.state
360 .read()
361 .expect("calc state poisoned")
362 .current
363 .get(&key)
364 .cloned()
365 .unwrap_or(Err(CalcError::NotCalculated { path: key }))
366 }
367
368 #[must_use]
371 pub fn last_good_cell(&self, path: &TablePath) -> Option<LastGoodValue> {
372 self.state
373 .read()
374 .expect("calc state poisoned")
375 .last_good
376 .get(&path_key(path))
377 .cloned()
378 .map(|value| LastGoodValue { value })
379 }
380
381 #[must_use]
384 pub fn current_is_volatile(&self, path: &TablePath) -> bool {
385 self.state
386 .read()
387 .expect("calc state poisoned")
388 .volatile
389 .contains(&path_key(path))
390 }
391
392 #[must_use]
394 pub fn cell_revision(&self, path: &TablePath) -> Option<u64> {
395 self.engine
396 .memo_revision(&CalcQuery::Cell(path_key(path)))
397 .map(|revision| revision.get())
398 }
399
400 #[must_use]
402 pub fn cell_fingerprint(&self, path: &TablePath) -> Option<ValueFingerprint> {
403 self.engine
404 .memo_fingerprint(&CalcQuery::Cell(path_key(path)))
405 }
406
407 #[must_use]
409 pub fn receipt(&self, path: &TablePath) -> Option<CalcReceipt> {
410 self.state
411 .read()
412 .expect("calc state poisoned")
413 .receipts
414 .get(&path_key(path))
415 .cloned()
416 }
417
418 #[must_use]
420 pub fn explain(&self, path: &TablePath) -> CalcExplanation {
421 let cell = path_key(path);
422 let policy = self.effective_calc_policy(path);
423 let authority = self.effective_authority(path);
424 let receipt = self.receipt(path);
425 let dirty = self
426 .engine
427 .dirty_keys()
428 .contains(&CalcQuery::Cell(cell.clone()));
429 let pending = self.automatic_queue.contains_key(&cell);
430 let has_current = self
431 .state
432 .read()
433 .expect("calc state poisoned")
434 .current
435 .contains_key(&cell);
436 let status = if policy.trigger == CalcTrigger::Frozen {
437 CalcStatus::Frozen
438 } else if pending {
439 CalcStatus::Pending
440 } else if matches!(
441 receipt.as_ref().map(|receipt| &receipt.outcome),
442 Some(CalcOutcome::Blocked { .. })
443 ) {
444 CalcStatus::Blocked
445 } else if matches!(
446 receipt.as_ref().map(|receipt| &receipt.outcome),
447 Some(CalcOutcome::Failed { .. })
448 ) {
449 CalcStatus::Failed
450 } else if dirty && has_current {
451 CalcStatus::MaybeStale
452 } else if has_current {
453 CalcStatus::Fresh
454 } else {
455 CalcStatus::NeverCalculated
456 };
457 let mut reasons = Vec::new();
458 match status {
459 CalcStatus::Frozen => reasons.push("effective trigger is frozen".to_owned()),
460 CalcStatus::Pending => reasons.push("bounded automatic work is queued".to_owned()),
461 CalcStatus::Blocked | CalcStatus::Failed => {
462 if let Some(receipt) = &receipt {
463 match &receipt.outcome {
464 CalcOutcome::Blocked { message } | CalcOutcome::Failed { message } => {
465 reasons.push(message.clone());
466 }
467 _ => {}
468 }
469 }
470 }
471 CalcStatus::MaybeStale => {
472 reasons.push("an observed input changed and awaits verification".to_owned());
473 }
474 CalcStatus::Fresh => {
475 reasons.push("the committed memo matches all observed revisions".to_owned());
476 }
477 CalcStatus::NeverCalculated => {
478 reasons.push("no calculation attempt has committed".to_owned());
479 }
480 }
481 CalcExplanation {
482 cell: cell.clone(),
483 status,
484 source_revision: self.engine.source_revision(&CalcQuery::Cell(cell)).get(),
485 policy_digest: policy.digest(),
486 authority_digest: authority.digest(),
487 receipt,
488 reasons,
489 }
490 }
491
492 pub fn run_automatic(&mut self, budget: AutomaticBudget, now_ms: u64) -> AutomaticRun {
494 self.run_automatic_inner(budget, now_ms)
495 }
496
497 pub fn continue_automatic(
499 &mut self,
500 continuation: AutomaticContinuation,
501 budget: AutomaticBudget,
502 now_ms: u64,
503 ) -> Result<AutomaticRun, CalcError> {
504 if continuation.generation() != self.automatic_generation {
505 return Err(CalcError::UnknownAutomaticContinuation {
506 generation: continuation.generation(),
507 });
508 }
509 Ok(self.run_automatic_inner(budget, now_ms))
510 }
511
512 #[must_use]
514 pub fn automatic_queue_snapshot(&self) -> AutomaticQueueSnapshot {
515 AutomaticQueueSnapshot {
516 generation: self.automatic_generation,
517 next_sequence: self.next_queue_sequence,
518 entries: self.automatic_queue.values().cloned().collect(),
519 }
520 }
521
522 pub fn restore_automatic_queue(
524 &mut self,
525 snapshot: AutomaticQueueSnapshot,
526 ) -> Result<(), CalcError> {
527 let mut restored = BTreeMap::new();
528 let mut next_request_id = self.next_request_id;
529 for entry in snapshot.entries {
530 if !entry.cell.starts_with('/') {
531 return Err(CalcError::CorruptAutomaticQueue { cell: entry.cell });
532 }
533 next_request_id = next_request_id.max(entry.request_id.get().saturating_add(1));
534 let cell = entry.cell.clone();
535 if restored.insert(cell.clone(), entry).is_some() {
536 return Err(CalcError::CorruptAutomaticQueue { cell });
537 }
538 }
539 self.automatic_queue = restored;
540 self.automatic_generation = snapshot.generation.max(1);
541 self.next_queue_sequence = snapshot.next_sequence.max(1);
542 self.next_request_id = next_request_id;
543 Ok(())
544 }
545}