1use std::{
2 collections::{BTreeMap, BTreeSet},
3 sync::{
4 Arc, RwLock,
5 atomic::{AtomicBool, AtomicU64, Ordering},
6 },
7};
8
9use sim_expr_tree_core::{
10 BackendKind, CodecPolicyPatch, EffectiveCodecPolicy, MountEpoch, MountResource,
11};
12use sim_incremental_core::{
13 ContinuationToken, IncrementalEngine, IncrementalError, ObservationKind, SnapshotBudgets,
14 ValueFingerprint,
15};
16use sim_kernel::{
17 CapabilitySet, Cx, DefaultFactory, EagerPolicy, Expr, HandleSeed, StrictNames, Symbol, Value,
18};
19use sim_lib_stream_core::BufferPolicy;
20use sim_table_core::TablePath;
21
22use crate::ExprTreeRefPolicy;
23
24mod attempt;
25mod engine;
26mod eval;
27use eval::{evaluate_cell, observe_runtime_context, parent_path, path_key};
28mod face;
29pub use face::{
30 EncodedFace, FaceContent, FaceDimension, FaceIssue, FaceMetadata, FacePosition,
31 SourceEditOutcome,
32};
33mod model;
34pub use model::{
35 CalcError, CalcLimits, CalcQuery, CellFailure, HARD_MAX_EXPR_DEPTH, HARD_MAX_OBSERVATIONS,
36 HARD_MAX_OUTPUT, HARD_MAX_QUERY_DEPTH, HARD_MAX_WORK, LastGoodValue,
37};
38use model::{ContextFactory, MemoOutcome, MemoValue};
39mod policy;
40pub use policy::{
41 AuthorityDigest, AuthorityPolicyPatch, CalcPolicyPatch, CalcTrigger, CycleMode,
42 EffectiveAuthority, EffectiveCalcPolicy, ErrorMode, PolicyDigest,
43};
44use policy::{
45 effective_authority, effective_calc_policy, effective_codec_policy, is_descendant_or_same,
46};
47mod persistence;
48pub use persistence::{
49 DERIVED_SNAPSHOT_KEY, DerivedPersistReport, DerivedRestoreDisposition, DerivedRestoreReport,
50 DerivedSnapshotError, DerivedTableAdapter, GRAPH_SCHEMA_VERSION,
51};
52mod refresh;
53pub use refresh::{BackendRefreshSample, MountRefreshSource, RefreshError, RefreshReport};
54mod receipt;
55pub use receipt::{
56 CalcExplanation, CalcOutcome, CalcReason, CalcReceipt, CalcRequestMode, CalcStatus,
57 DependencyStamp, DirectedCalcReport, DirectedCellResult, EffectStamp, RequestId,
58};
59mod scheduler;
60use scheduler::MAX_READY_BYPASSES;
61pub use scheduler::{
62 AutomaticBudget, AutomaticContinuation, AutomaticQueueSnapshot, AutomaticRun, QueuedCalculation,
63};
64mod scheduling;
65mod session;
66mod value;
67mod watch;
68pub use watch::CalcWatch;
69
70const MAX_RECEIPT_DEPENDENCIES: usize = 64;
71const MAX_RECEIPT_GRAPH_NODES: usize = 4_096;
72const MAX_RECEIPT_GRAPH_EDGES: usize = 65_536;
73
74use sim_host_core::WallClock;
75
76pub struct ExprTreeCalc {
79 state: Arc<RwLock<CalcState>>,
80 engine: IncrementalEngine<CalcQuery, MemoValue>,
81 context_factory: Arc<ContextFactory>,
82 cancel_requested: Arc<AtomicBool>,
83 next_volatile: Arc<AtomicU64>,
84 wall_clock: Arc<RwLock<Option<Arc<dyn WallClock>>>>,
85 next_request_id: u64,
86 automatic_queue: BTreeMap<String, QueuedCalculation>,
87 automatic_generation: u64,
88 next_queue_sequence: u64,
89 watches: Vec<CalcWatch>,
90 next_watch_id: u64,
91 refresh_sources: BTreeMap<String, Arc<dyn MountRefreshSource>>,
92 refresh_samples: BTreeMap<String, BackendRefreshSample>,
93 restored_continuations: BTreeSet<ContinuationToken>,
94}
95
96#[derive(Default)]
97pub(crate) struct CalcState {
98 cells: BTreeMap<String, Expr>,
99 bound_names: BTreeSet<String>,
100 bound_values: BTreeMap<Symbol, Value>,
101 mounts: BTreeMap<String, MountState>,
102 codec_registry_revision: u64,
103 tree_calc_policy: CalcPolicyPatch,
104 dir_calc_policies: BTreeMap<String, CalcPolicyPatch>,
105 cell_calc_policies: BTreeMap<String, CalcPolicyPatch>,
106 tree_codec_policy: CodecPolicyPatch,
107 dir_codec_policies: BTreeMap<String, CodecPolicyPatch>,
108 cell_codec_policies: BTreeMap<String, CodecPolicyPatch>,
109 authority_ceiling: CapabilitySet,
110 tree_authority_policy: AuthorityPolicyPatch,
111 dir_authority_policies: BTreeMap<String, AuthorityPolicyPatch>,
112 cell_authority_policies: BTreeMap<String, AuthorityPolicyPatch>,
113 active_request: Option<ActiveRequest>,
114 attempts: Vec<AttemptDraft>,
115 receipts: BTreeMap<String, CalcReceipt>,
116 next_logical_tick: u64,
117 current: BTreeMap<String, Result<Value, CalcError>>,
118 last_good: BTreeMap<String, Value>,
119 volatile: BTreeSet<String>,
120 failed_cells: BTreeSet<String>,
121 source_generation: u64,
122 control_generation: u64,
123}
124
125#[derive(Clone)]
126struct ActiveRequest {
127 id: RequestId,
128 reason: CalcReason,
129 directed_cells: BTreeSet<String>,
130 automatic: bool,
131}
132
133struct AttemptDraft {
134 request_id: RequestId,
135 cell: String,
136 policy: EffectiveCalcPolicy,
137 authority: EffectiveAuthority,
138 started_tick: u64,
139 finished_tick: u64,
140 wall_started_ms: Option<u64>,
141 wall_finished_ms: Option<u64>,
142 outcome: CalcOutcome,
143 effects: Vec<EffectStamp>,
144 omitted_effects: usize,
145 reason: CalcReason,
146}
147
148#[derive(Clone, Debug, PartialEq, Eq)]
149struct MountState {
150 resource: MountResource,
151 backend: BackendKind,
152 epoch: MountEpoch,
153}
154
155impl ExprTreeCalc {
156 #[must_use]
158 pub fn new(first_handle_seed: HandleSeed) -> Self {
159 let next_handle_seed = Arc::new(AtomicU64::new(first_handle_seed.0));
160 Self::with_context_factory(move || {
161 let seed = next_handle_seed
162 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |seed| {
163 seed.checked_add(1)
164 })
165 .expect("expression-tree handle seed space exhausted");
166 Cx::new(
167 Arc::new(ExprTreeRefPolicy::new(StrictNames(EagerPolicy))),
168 Arc::new(DefaultFactory),
169 HandleSeed::new(seed),
170 )
171 })
172 }
173
174 #[must_use]
180 pub fn with_context_factory<F>(factory: F) -> Self
181 where
182 F: Fn() -> Cx + Send + Sync + 'static,
183 {
184 let context_factory: Arc<ContextFactory> = Arc::new(factory);
185 let open_time_authority = context_factory().capabilities().clone();
186 Self {
187 state: Arc::new(RwLock::new(CalcState {
188 authority_ceiling: open_time_authority,
189 next_logical_tick: 1,
190 ..CalcState::default()
191 })),
192 engine: IncrementalEngine::new(),
193 context_factory,
194 cancel_requested: Arc::new(AtomicBool::new(false)),
195 next_volatile: Arc::new(AtomicU64::new(1)),
196 wall_clock: Arc::new(RwLock::new(None)),
197 next_request_id: 1,
198 automatic_queue: BTreeMap::new(),
199 automatic_generation: 1,
200 next_queue_sequence: 1,
201 watches: Vec::new(),
202 next_watch_id: 1,
203 refresh_sources: BTreeMap::new(),
204 refresh_samples: BTreeMap::new(),
205 restored_continuations: BTreeSet::new(),
206 }
207 }
208
209 pub fn set_wall_clock(&mut self, clock: Arc<dyn WallClock>) {
213 *self.wall_clock.write().expect("wall clock lock poisoned") = Some(clock);
214 }
215
216 #[must_use]
218 pub fn open_time_authority(&self) -> CapabilitySet {
219 self.state
220 .read()
221 .expect("calc state poisoned")
222 .authority_ceiling
223 .clone()
224 }
225
226 pub fn set_cell(&mut self, path: TablePath, source: Expr) {
228 let key = path_key(&path);
229 let (replaced, failed) = {
230 let mut state = self.state.write().expect("calc state poisoned");
231 let replaced = state.cells.insert(key.clone(), source).is_some();
232 bump_generation(&mut state.source_generation);
233 state.current.remove(&key);
234 state.volatile.remove(&key);
235 (
236 replaced,
237 state.failed_cells.iter().cloned().collect::<Vec<_>>(),
238 )
239 };
240 if !replaced {
241 self.register_cell_query(key.clone());
242 }
243 self.invalidate_cell_source(&path, !replaced);
244 self.invalidate_failed_cells(failed);
245 self.emit_change("source-set", &key);
246 self.schedule_dirty_automatic();
247 }
248
249 pub fn remove_cell(&mut self, path: &TablePath) {
251 let key = path_key(path);
252 let failed = {
253 let mut state = self.state.write().expect("calc state poisoned");
254 state.cells.remove(&key);
255 bump_generation(&mut state.source_generation);
256 state.current.remove(&key);
257 state.volatile.remove(&key);
258 state.failed_cells.iter().cloned().collect::<Vec<_>>()
259 };
260 self.register_cell_query(key.clone());
261 self.invalidate_cell_source(path, true);
262 self.invalidate_failed_cells(failed);
263 self.emit_change("source-removed", &key);
264 self.schedule_dirty_automatic();
265 }
266
267 pub fn move_cell(&mut self, from: &TablePath, to: TablePath) {
269 let from_key = path_key(from);
270 let to_key = path_key(&to);
271 let (moved, failed) = {
272 let mut state = self.state.write().expect("calc state poisoned");
273 let moved = state.cells.remove(&from_key);
274 if let Some(source) = moved.clone() {
275 state.cells.insert(to_key.clone(), source);
276 }
277 bump_generation(&mut state.source_generation);
278 state.current.remove(&from_key);
279 state.current.remove(&to_key);
280 state.volatile.remove(&from_key);
281 state.volatile.remove(&to_key);
282 let failed = state.failed_cells.iter().cloned().collect::<Vec<_>>();
283 (moved, failed)
284 };
285 if moved.is_some() {
286 self.register_cell_query(to_key.clone());
287 }
288 self.register_cell_query(from_key.clone());
289 self.invalidate_cell_source(from, true);
290 self.invalidate_cell_source(&to, true);
291 self.invalidate_failed_cells(failed);
292 self.emit_change("source-moved-from", &from_key);
293 self.emit_change("source-moved-to", &to_key);
294 self.schedule_dirty_automatic();
295 }
296
297 pub fn bind_name(&mut self, name: impl Into<String>) {
302 let name = name.into();
303 {
304 let mut state = self.state.write().expect("calc state poisoned");
305 state.bound_names.insert(name.clone());
306 bump_generation(&mut state.source_generation);
307 }
308 self.engine.invalidate(&CalcQuery::NameSlot(name));
309 }
310
311 pub fn bind_value(&mut self, name: Symbol, value: Value) {
313 {
314 let mut state = self.state.write().expect("calc state poisoned");
315 state.bound_values.insert(name.clone(), value);
316 bump_generation(&mut state.source_generation);
317 }
318 self.engine
319 .invalidate(&CalcQuery::NameSlot(name.to_string()));
320 }
321
322 pub fn cell_dependencies(
324 &mut self,
325 path: &TablePath,
326 ) -> Result<Vec<(CalcQuery, ObservationKind)>, IncrementalError<CalcQuery>> {
327 let key = CalcQuery::Cell(path_key(path));
328 let snapshot = self
329 .engine
330 .snapshot([key.clone()], SnapshotBudgets::default())?;
331 Ok(snapshot
332 .nodes
333 .iter()
334 .find(|node| node.key == key)
335 .map(|node| {
336 node.dependencies
337 .iter()
338 .map(|observation| (observation.key().clone(), observation.kind().clone()))
339 .collect()
340 })
341 .unwrap_or_default())
342 }
343
344 #[cfg(test)]
345 pub(crate) fn replace_context_factory<F>(&mut self, factory: F)
346 where
347 F: Fn() -> Cx + Send + Sync + 'static,
348 {
349 self.context_factory = Arc::new(factory);
350 }
351
352 #[cfg(test)]
353 pub(crate) fn state_for_lock_probe(&self) -> Arc<RwLock<CalcState>> {
354 Arc::clone(&self.state)
355 }
356}
357
358fn incremental_failure(
359 error: IncrementalError<CalcQuery>,
360) -> Result<MemoValue, IncrementalError<CalcQuery>> {
361 match error {
362 IncrementalError::Cycle { path } => Ok(MemoValue::failure(CellFailure::Cycle { path })),
363 IncrementalError::UnknownQuery { key } => Ok(MemoValue::failure(CellFailure::Evaluation {
364 message: format!("unknown dependency {key:?}"),
365 })),
366 IncrementalError::BudgetExceeded { .. }
367 | IncrementalError::Cancelled
368 | IncrementalError::UnknownContinuation { .. } => Err(error),
369 }
370}
371
372pub(super) fn bump_generation(generation: &mut u64) {
373 *generation = generation.saturating_add(1);
374}