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