1use std::collections::{BTreeMap, BTreeSet};
2use std::time::{Duration, Instant};
3
4use formualizer_common::RangeAddress;
5
6use super::{EvaluationBudgets, VertexId};
7use crate::formula_plane::region_index::Region;
8use crate::formula_plane::runtime::FormulaSpanRef;
9use crate::reference::CellRef;
10
11pub type RequestId = u64;
12
13#[cfg(test)]
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
15pub(crate) enum TargetPreparationFault {
16 #[default]
17 None,
18 AfterDiscovery,
19 FinalRevisionValidation,
20 FinalGraphValidation,
21 Admission,
22 Reservation,
23 BeforeFirstMutation,
24}
25
26#[derive(Clone, Debug, PartialEq, Eq, Hash)]
27pub enum EvaluationTarget {
28 Cell {
29 sheet: String,
30 row: u32,
31 col: u32,
32 },
33 Range(RangeAddress),
34 Name {
35 name: String,
36 scope_sheet: Option<String>,
37 },
38 Table {
39 name: String,
40 selection: TableSelection,
41 },
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
45pub(crate) enum TargetProducer {
46 Legacy(VertexId),
47 Span {
48 span_ref: FormulaSpanRef,
49 demanded: Region,
50 },
51 Symbol(VertexId),
52 ValueOnly(CellRef),
53}
54
55#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
56pub enum TableSelection {
57 #[default]
58 Whole,
59 Headers,
60 Data,
61 Totals,
62 Column(String),
63 Columns {
64 start: String,
65 end: String,
66 },
67}
68
69#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
70pub enum OpaquePreparePolicy {
71 #[default]
72 Widen,
73 Error,
74}
75
76#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
77#[non_exhaustive]
78pub enum PrepareScope {
79 #[default]
80 Exact,
81 Sheets(Vec<String>),
82 Workbook,
83}
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
86#[non_exhaustive]
87pub enum OpaqueReason {
88 DynamicReference,
89 RuntimeTextReference,
90 UnknownFunction,
91 UnknownCustomFunction,
92 UnresolvedCrossSheetBinding,
93 UnresolvedName,
94 UnresolvedTable,
95 FormulaName,
96 DeferredSourcePackage,
97 UnsupportedSourceSemantics,
98 UncertainDefaultSheetBinding,
99}
100
101#[derive(Clone, Debug)]
105pub struct TargetEvalOptions<'a> {
106 pub request_id: Option<RequestId>,
107 pub cancel: Option<crate::engine::CancelToken>,
108 pub deadline: Option<Instant>,
109 pub budgets: Option<&'a EvaluationBudgets>,
110 pub opaque_policy: OpaquePreparePolicy,
111}
112
113impl Default for TargetEvalOptions<'_> {
114 fn default() -> Self {
115 Self {
116 request_id: None,
117 cancel: None,
118 deadline: None,
119 budgets: None,
120 opaque_policy: OpaquePreparePolicy::Widen,
121 }
122 }
123}
124
125#[derive(Clone, Debug, Default, PartialEq, Eq)]
126#[non_exhaustive]
127pub struct PreparationRevision {
128 pub graph: u64,
129 pub authority: u64,
132 pub authority_indexes: u64,
133 pub authority_indexed_plane: u64,
134 pub staged: u64,
135 pub symbols: u64,
136 pub semantic: u64,
137 pub provider: Option<u64>,
138}
139
140#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
141#[non_exhaustive]
142pub enum PreparationOutcome {
143 #[default]
144 Prepared,
145 CompatibilityPrepared,
146}
147
148#[derive(Clone, Debug, Default, PartialEq, Eq)]
149#[non_exhaustive]
150pub struct PreparedTargetGraphReport {
151 pub request_id: RequestId,
152 pub requested_targets: usize,
153 pub normalized_regions: usize,
154 pub normalized_target_list: Vec<EvaluationTarget>,
155 pub selected_staged_cells: usize,
156 pub selected_source_families: usize,
159 pub retained_staged_cells: usize,
160 pub selected_cells: Vec<RangeAddress>,
161 pub retained_cells: Vec<RangeAddress>,
162 pub widened_scope: PrepareScope,
163 pub widening_reasons: Vec<OpaqueReason>,
164 pub revisions: PreparationRevision,
165 pub commit_window: Duration,
166 pub estimated_scratch_bytes: u64,
167 pub observed_scratch_bytes: u64,
168 pub estimated_commit_work: u64,
169 pub actual_commit_work: u64,
170 pub outcome: PreparationOutcome,
171}
172
173#[derive(Clone, Copy, Debug, PartialEq, Eq)]
174pub(crate) struct StagedFormulaLease {
175 pub(crate) row: u32,
176 pub(crate) col: u32,
177 pub(crate) generation: u64,
178 pub(crate) insertion_order: u64,
179}
180
181#[derive(Clone, Copy, Debug, PartialEq, Eq)]
182struct StagedFormulaPresence {
183 generation: u64,
184 insertion_order: u64,
185}
186
187#[derive(Clone, Copy, Debug, PartialEq, Eq)]
188pub(crate) struct StagedPackageLease {
189 pub(crate) generation: u64,
190 pub(crate) family_count: usize,
191}
192
193#[derive(Clone, Copy, Debug, PartialEq, Eq)]
194struct StagedPackageRect {
195 start_row: u32,
196 start_col: u32,
197 end_row: u32,
198 end_col: u32,
199}
200
201impl StagedPackageRect {
202 fn intersects(self, start_row: u32, start_col: u32, end_row: u32, end_col: u32) -> bool {
203 self.start_row <= end_row
204 && start_row <= self.end_row
205 && self.start_col <= end_col
206 && start_col <= self.end_col
207 }
208}
209
210#[derive(Clone, Debug)]
211struct StagedPackagePresence {
212 generation: u64,
213 family_count: usize,
214 geometry: Vec<StagedPackageRect>,
215 fallback_points: BTreeSet<(u32, u32)>,
216 geometry_complete: bool,
217}
218
219#[derive(Clone, Debug, Default)]
220pub(crate) struct StagedFormulaIndex {
221 revision: u64,
222 next_generation: u64,
223 next_insertion_order: u64,
224 sheets: BTreeMap<String, BTreeMap<(u32, u32), StagedFormulaPresence>>,
225 packages: BTreeMap<String, StagedPackagePresence>,
226}
227
228impl StagedFormulaIndex {
229 fn bump(&mut self) {
230 self.revision = self
231 .revision
232 .checked_add(1)
233 .expect("staged formula index revision exhausted");
234 }
235
236 pub(crate) fn revision(&self) -> u64 {
237 self.revision
238 }
239
240 pub(crate) fn stage(&mut self, sheet: &str, row: u32, col: u32) {
241 let generation = self.next_generation;
242 self.next_generation = self
243 .next_generation
244 .checked_add(1)
245 .expect("staged formula generation exhausted");
246 let entries = self.sheets.entry(sheet.to_string()).or_default();
247 let insertion_order = entries.get(&(row, col)).map_or_else(
248 || {
249 let order = self.next_insertion_order;
250 self.next_insertion_order = self
251 .next_insertion_order
252 .checked_add(1)
253 .expect("staged formula insertion order exhausted");
254 order
255 },
256 |entry| entry.insertion_order,
257 );
258 entries.insert(
259 (row, col),
260 StagedFormulaPresence {
261 generation,
262 insertion_order,
263 },
264 );
265 self.bump();
266 }
267
268 pub(crate) fn remove(&mut self, sheet: &str, row: u32, col: u32) -> bool {
269 let removed = self
270 .sheets
271 .get_mut(sheet)
272 .is_some_and(|entries| entries.remove(&(row, col)).is_some());
273 if self.sheets.get(sheet).is_some_and(BTreeMap::is_empty) {
274 self.sheets.remove(sheet);
275 }
276 if removed {
277 self.bump();
278 }
279 removed
280 }
281
282 pub(crate) fn clear_sheet(&mut self, sheet: &str) {
283 let changed = self.sheets.remove(sheet).is_some() | self.packages.remove(sheet).is_some();
284 if changed {
285 self.bump();
286 }
287 }
288
289 pub(crate) fn clear_all(&mut self) {
290 if !self.sheets.is_empty() || !self.packages.is_empty() {
291 self.sheets.clear();
292 self.packages.clear();
293 self.bump();
294 }
295 }
296
297 pub(crate) fn set_package(
298 &mut self,
299 sheet: &str,
300 package: Option<&super::DeferredFormulaPackage>,
301 ) {
302 let changed = if let Some(package) = package {
303 let generation = self.next_generation;
304 self.next_generation = self
305 .next_generation
306 .checked_add(1)
307 .expect("staged package generation exhausted");
308 let mut geometry = Vec::new();
309 for family in &package.families {
310 match &family.members {
311 super::SourceFamilyMembers::CompleteDomain(domain) => {
312 let rect = domain.rect();
313 geometry.push(StagedPackageRect {
314 start_row: rect.start.row.saturating_add(1),
315 start_col: rect.start.col.saturating_add(1),
316 end_row: rect.end.row.saturating_add(1),
317 end_col: rect.end.col.saturating_add(1),
318 });
319 }
320 super::SourceFamilyMembers::ExplicitMembers(members) => {
321 geometry.extend(members.as_slice().iter().map(|coord| StagedPackageRect {
322 start_row: coord.row.saturating_add(1),
323 start_col: coord.col.saturating_add(1),
324 end_row: coord.row.saturating_add(1),
325 end_col: coord.col.saturating_add(1),
326 }));
327 }
328 }
329 }
330 geometry.extend(
331 package
332 .partitioned_families
333 .iter()
334 .map(|family| StagedPackageRect {
335 start_row: family.declared.start.row.saturating_add(1),
336 start_col: family.declared.start.col.saturating_add(1),
337 end_row: family.declared.end.row.saturating_add(1),
338 end_col: family.declared.end.col.saturating_add(1),
339 }),
340 );
341 let fallback_points = package
342 .source_coordinates
343 .iter()
344 .map(|coord| (coord.row.saturating_add(1), coord.col.saturating_add(1)))
345 .collect();
346 let geometry_complete = package.source_geometry_complete
347 || package.report.source_formula_records_spooled == 0;
348 self.packages.insert(
349 sheet.to_string(),
350 StagedPackagePresence {
351 generation,
352 family_count: package.families.len() + package.partitioned_families.len(),
353 geometry,
354 fallback_points,
355 geometry_complete,
356 },
357 );
358 true
359 } else {
360 self.packages.remove(sheet).is_some()
361 };
362 if changed {
363 self.bump();
364 }
365 }
366
367 pub(crate) fn touch_package(&mut self, sheet: &str) {
368 if let Some(package) = self.packages.get_mut(sheet) {
369 package.generation = self.next_generation;
370 self.next_generation = self
371 .next_generation
372 .checked_add(1)
373 .expect("staged package generation exhausted");
374 self.bump();
375 }
376 }
377
378 pub(crate) fn has_packages(&self) -> bool {
379 !self.packages.is_empty()
380 }
381
382 pub(crate) fn package_sheets(&self) -> impl Iterator<Item = &str> {
383 self.packages.keys().map(String::as_str)
384 }
385
386 pub(crate) fn package_for_region(
387 &self,
388 sheet: &str,
389 start_row: u32,
390 start_col: u32,
391 end_row: u32,
392 end_col: u32,
393 ) -> Option<Result<StagedPackageLease, ()>> {
394 let package = self.packages.get(sheet)?;
395 let intersects = package
396 .geometry
397 .iter()
398 .copied()
399 .any(|rect| rect.intersects(start_row, start_col, end_row, end_col))
400 || package
401 .fallback_points
402 .range((start_row, 0)..=(end_row, u32::MAX))
403 .any(|&(row, col)| col >= start_col && col <= end_col);
404 if intersects {
405 Some(Ok(StagedPackageLease {
406 generation: package.generation,
407 family_count: package.family_count,
408 }))
409 } else if package.geometry_complete {
410 None
411 } else {
412 Some(Err(()))
413 }
414 }
415
416 pub(crate) fn package_lease_for_sheet(&self, sheet: &str) -> Option<StagedPackageLease> {
417 self.packages.get(sheet).map(|package| StagedPackageLease {
418 generation: package.generation,
419 family_count: package.family_count,
420 })
421 }
422
423 pub(crate) fn package_lease_matches(&self, sheet: &str, lease: StagedPackageLease) -> bool {
424 self.packages.get(sheet).is_some_and(|package| {
425 package.generation == lease.generation && package.family_count == lease.family_count
426 })
427 }
428
429 pub(crate) fn leases_in_region(
430 &self,
431 sheet: &str,
432 start_row: u32,
433 start_col: u32,
434 end_row: u32,
435 end_col: u32,
436 ) -> Vec<StagedFormulaLease> {
437 let mut leases = self
438 .sheets
439 .get(sheet)
440 .into_iter()
441 .flat_map(|entries| entries.range((start_row, 0)..=(end_row, u32::MAX)))
442 .filter_map(|(&(row, col), entry)| {
443 (col >= start_col && col <= end_col).then_some(StagedFormulaLease {
444 row,
445 col,
446 generation: entry.generation,
447 insertion_order: entry.insertion_order,
448 })
449 })
450 .collect::<Vec<_>>();
451 leases.sort_by_key(|lease| lease.insertion_order);
452 leases
453 }
454
455 pub(crate) fn leases_for_sheet(&self, sheet: &str) -> Vec<StagedFormulaLease> {
456 self.leases_in_region(sheet, 1, 1, u32::MAX, u32::MAX)
457 }
458
459 pub(crate) fn all_leases(&self) -> Vec<(String, StagedFormulaLease)> {
460 let mut leases = self
461 .sheets
462 .iter()
463 .flat_map(|(sheet, entries)| {
464 entries.iter().map(move |(&(row, col), entry)| {
465 (
466 sheet.clone(),
467 StagedFormulaLease {
468 row,
469 col,
470 generation: entry.generation,
471 insertion_order: entry.insertion_order,
472 },
473 )
474 })
475 })
476 .collect::<Vec<_>>();
477 leases.sort_by_key(|(_, lease)| lease.insertion_order);
478 leases
479 }
480
481 pub(crate) fn lease_matches(&self, sheet: &str, lease: StagedFormulaLease) -> bool {
482 self.sheets
483 .get(sheet)
484 .and_then(|entries| entries.get(&(lease.row, lease.col)))
485 .is_some_and(|entry| {
486 entry.generation == lease.generation
487 && entry.insertion_order == lease.insertion_order
488 })
489 }
490
491 pub(crate) fn ordinary_count(&self) -> usize {
492 self.sheets.values().map(BTreeMap::len).sum()
493 }
494
495 #[cfg(test)]
496 pub(crate) fn package_count(&self) -> usize {
497 self.packages.len()
498 }
499}
500
501#[deprecated(since = "0.8.0", note = "renamed to TargetEvalOptions")]
504pub type PrepareTargetsOptions<'a> = TargetEvalOptions<'a>;