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,
160 pub retained_staged_cells: usize,
161 pub selected_cells: Vec<RangeAddress>,
162 pub retained_cells: Vec<RangeAddress>,
163 pub widened_scope: PrepareScope,
164 pub widening_reasons: Vec<OpaqueReason>,
165 pub revisions: PreparationRevision,
166 pub commit_window: Duration,
167 pub estimated_scratch_bytes: u64,
168 pub observed_scratch_bytes: u64,
169 pub estimated_commit_work: u64,
170 pub actual_commit_work: u64,
171 pub outcome: PreparationOutcome,
172}
173
174#[derive(Clone, Copy, Debug, PartialEq, Eq)]
175pub(crate) struct StagedFormulaLease {
176 pub(crate) row: u32,
177 pub(crate) col: u32,
178 pub(crate) generation: u64,
179 pub(crate) insertion_order: u64,
180}
181
182#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183struct StagedFormulaPresence {
184 generation: u64,
185 insertion_order: u64,
186}
187
188#[derive(Clone, Copy, Debug, PartialEq, Eq)]
189pub(crate) struct StagedPackageLease {
190 pub(crate) generation: u64,
191 pub(crate) family_count: usize,
192}
193
194#[derive(Clone, Copy, Debug, PartialEq, Eq)]
195struct StagedPackageRect {
196 start_row: u32,
197 start_col: u32,
198 end_row: u32,
199 end_col: u32,
200}
201
202impl StagedPackageRect {
203 fn intersects(self, start_row: u32, start_col: u32, end_row: u32, end_col: u32) -> bool {
204 self.start_row <= end_row
205 && start_row <= self.end_row
206 && self.start_col <= end_col
207 && start_col <= self.end_col
208 }
209}
210
211#[derive(Clone, Debug)]
212struct StagedPackagePresence {
213 generation: u64,
214 family_count: usize,
215 geometry: Vec<StagedPackageRect>,
216 occupancy_geometry: Vec<StagedPackageRect>,
218 fallback_points: BTreeSet<(u32, u32)>,
219 geometry_complete: bool,
220}
221
222#[derive(Clone, Debug, Default)]
223pub(crate) struct StagedFormulaIndex {
224 revision: u64,
225 next_generation: u64,
226 next_insertion_order: u64,
227 sheets: BTreeMap<String, BTreeMap<(u32, u32), StagedFormulaPresence>>,
228 packages: BTreeMap<String, StagedPackagePresence>,
229}
230
231impl StagedFormulaIndex {
232 fn bump(&mut self) {
233 self.revision = self
234 .revision
235 .checked_add(1)
236 .expect("staged formula index revision exhausted");
237 }
238
239 pub(crate) fn revision(&self) -> u64 {
240 self.revision
241 }
242
243 pub(crate) fn stage(&mut self, sheet: &str, row: u32, col: u32) {
244 let generation = self.next_generation;
245 self.next_generation = self
246 .next_generation
247 .checked_add(1)
248 .expect("staged formula generation exhausted");
249 let entries = self.sheets.entry(sheet.to_string()).or_default();
250 let insertion_order = entries.get(&(row, col)).map_or_else(
251 || {
252 let order = self.next_insertion_order;
253 self.next_insertion_order = self
254 .next_insertion_order
255 .checked_add(1)
256 .expect("staged formula insertion order exhausted");
257 order
258 },
259 |entry| entry.insertion_order,
260 );
261 entries.insert(
262 (row, col),
263 StagedFormulaPresence {
264 generation,
265 insertion_order,
266 },
267 );
268 self.bump();
269 }
270
271 pub(crate) fn remove(&mut self, sheet: &str, row: u32, col: u32) -> bool {
272 let removed = self
273 .sheets
274 .get_mut(sheet)
275 .is_some_and(|entries| entries.remove(&(row, col)).is_some());
276 if self.sheets.get(sheet).is_some_and(BTreeMap::is_empty) {
277 self.sheets.remove(sheet);
278 }
279 if removed {
280 self.bump();
281 }
282 removed
283 }
284
285 pub(crate) fn clear_sheet(&mut self, sheet: &str) {
286 let changed = self.sheets.remove(sheet).is_some() | self.packages.remove(sheet).is_some();
287 if changed {
288 self.bump();
289 }
290 }
291
292 pub(crate) fn clear_all(&mut self) {
293 if !self.sheets.is_empty() || !self.packages.is_empty() {
294 self.sheets.clear();
295 self.packages.clear();
296 self.bump();
297 }
298 }
299
300 pub(crate) fn set_package(
301 &mut self,
302 sheet: &str,
303 package: Option<&super::DeferredFormulaPackage>,
304 ) {
305 let changed = if let Some(package) = package {
306 let generation = self.next_generation;
307 self.next_generation = self
308 .next_generation
309 .checked_add(1)
310 .expect("staged package generation exhausted");
311 let mut geometry = Vec::new();
312 for family in &package.families {
313 match &family.members {
314 super::SourceFamilyMembers::CompleteDomain(domain) => {
315 let rect = domain.rect();
316 geometry.push(StagedPackageRect {
317 start_row: rect.start.row.saturating_add(1),
318 start_col: rect.start.col.saturating_add(1),
319 end_row: rect.end.row.saturating_add(1),
320 end_col: rect.end.col.saturating_add(1),
321 });
322 }
323 super::SourceFamilyMembers::ExplicitMembers(members) => {
324 geometry.extend(members.as_slice().iter().map(|coord| StagedPackageRect {
325 start_row: coord.row.saturating_add(1),
326 start_col: coord.col.saturating_add(1),
327 end_row: coord.row.saturating_add(1),
328 end_col: coord.col.saturating_add(1),
329 }));
330 }
331 }
332 }
333 let mut occupancy_geometry = geometry.clone();
334 for family in &package.partitioned_families {
335 occupancy_geometry.extend(family.fragments.iter().map(|fragment| {
336 let rect = fragment.rect();
337 StagedPackageRect {
338 start_row: rect.start.row + 1,
339 start_col: rect.start.col + 1,
340 end_row: rect.end.row + 1,
341 end_col: rect.end.col + 1,
342 }
343 }));
344 occupancy_geometry.extend(family.legacy_members.as_slice().iter().map(|member| {
347 StagedPackageRect {
348 start_row: member.coord.row + 1,
349 start_col: member.coord.col + 1,
350 end_row: member.coord.row + 1,
351 end_col: member.coord.col + 1,
352 }
353 }));
354 }
355 geometry.extend(
356 package
357 .partitioned_families
358 .iter()
359 .map(|family| StagedPackageRect {
360 start_row: family.declared.start.row.saturating_add(1),
361 start_col: family.declared.start.col.saturating_add(1),
362 end_row: family.declared.end.row.saturating_add(1),
363 end_col: family.declared.end.col.saturating_add(1),
364 }),
365 );
366 let fallback_points = package
367 .source_coordinates
368 .iter()
369 .map(|coord| (coord.row.saturating_add(1), coord.col.saturating_add(1)))
370 .filter(|point| !package.source_accounted || !package.suppressed.contains(point))
371 .collect();
372 let geometry_complete = package.source_geometry_complete
373 || package.report.source_formula_records_spooled == 0;
374 self.packages.insert(
375 sheet.to_string(),
376 StagedPackagePresence {
377 generation,
378 family_count: package.families.len() + package.partitioned_families.len(),
379 geometry,
380 occupancy_geometry,
381 fallback_points,
382 geometry_complete,
383 },
384 );
385 true
386 } else {
387 self.packages.remove(sheet).is_some()
388 };
389 if changed {
390 self.bump();
391 }
392 }
393
394 pub(crate) fn occupies_spill(
398 &self,
399 sheet: &str,
400 anchor: (u32, u32),
401 end: (u32, u32),
402 suppressed: impl Fn((u32, u32)) -> bool,
403 ) -> bool {
404 if self.sheets.get(sheet).is_some_and(|entries| {
405 entries
406 .range((anchor.0, 0)..=(end.0, u32::MAX))
407 .any(|(&point, _)| point != anchor && point.1 >= anchor.1 && point.1 <= end.1)
408 }) {
409 return true;
410 }
411 self.package_occupies_spill(sheet, anchor, end, suppressed)
412 }
413
414 pub(crate) fn package_occupies_spill(
415 &self,
416 sheet: &str,
417 anchor: (u32, u32),
418 end: (u32, u32),
419 suppressed: impl Fn((u32, u32)) -> bool,
420 ) -> bool {
421 let Some(package) = self.packages.get(sheet) else {
422 return false;
423 };
424 if package
425 .fallback_points
426 .range((anchor.0, 0)..=(end.0, u32::MAX))
427 .any(|&point| {
428 point != anchor && point.1 >= anchor.1 && point.1 <= end.1 && !suppressed(point)
429 })
430 {
431 return true;
432 }
433 package.occupancy_geometry.iter().any(|rect| {
434 if !rect.intersects(anchor.0, anchor.1, end.0, end.1) {
435 return false;
436 }
437 for row in rect.start_row.max(anchor.0)..=rect.end_row.min(end.0) {
440 for col in rect.start_col.max(anchor.1)..=rect.end_col.min(end.1) {
441 let point = (row, col);
442 if point != anchor && !suppressed(point) {
443 return true;
444 }
445 }
446 }
447 false
448 })
449 }
450
451 pub(crate) fn package_points_in_region(
452 &self,
453 sheet: &str,
454 start_row: u32,
455 start_col: u32,
456 end_row: u32,
457 end_col: u32,
458 ) -> Vec<(u32, u32)> {
459 self.packages
460 .get(sheet)
461 .into_iter()
462 .flat_map(|package| {
463 package
464 .fallback_points
465 .range((start_row, 0)..=(end_row, u32::MAX))
466 })
467 .filter(|&&(_, col)| col >= start_col && col <= end_col)
468 .copied()
469 .collect()
470 }
471
472 pub(crate) fn consume_package_points(&mut self, sheet: &str, points: &BTreeSet<(u32, u32)>) {
473 if let Some(package) = self.packages.get_mut(sheet) {
474 for point in points {
475 package.fallback_points.remove(point);
476 }
477 }
478 self.touch_package(sheet);
479 }
480
481 pub(crate) fn update_package_family_count(&mut self, sheet: &str, count: usize) {
482 if let Some(package) = self.packages.get_mut(sheet) {
483 package.family_count = count;
484 }
485 }
486
487 pub(crate) fn touch_package(&mut self, sheet: &str) {
488 if let Some(package) = self.packages.get_mut(sheet) {
489 package.generation = self.next_generation;
490 self.next_generation = self
491 .next_generation
492 .checked_add(1)
493 .expect("staged package generation exhausted");
494 self.bump();
495 }
496 }
497
498 pub(crate) fn has_packages(&self) -> bool {
499 !self.packages.is_empty()
500 }
501
502 pub(crate) fn package_sheets(&self) -> impl Iterator<Item = &str> {
503 self.packages.keys().map(String::as_str)
504 }
505
506 pub(crate) fn package_for_region(
507 &self,
508 sheet: &str,
509 start_row: u32,
510 start_col: u32,
511 end_row: u32,
512 end_col: u32,
513 ) -> Option<Result<StagedPackageLease, ()>> {
514 let package = self.packages.get(sheet)?;
515 let intersects = package
516 .geometry
517 .iter()
518 .copied()
519 .any(|rect| rect.intersects(start_row, start_col, end_row, end_col))
520 || package
521 .fallback_points
522 .range((start_row, 0)..=(end_row, u32::MAX))
523 .any(|&(row, col)| col >= start_col && col <= end_col);
524 if intersects {
525 Some(Ok(StagedPackageLease {
526 generation: package.generation,
527 family_count: package.family_count,
528 }))
529 } else if package.geometry_complete {
530 None
531 } else {
532 Some(Err(()))
533 }
534 }
535
536 pub(crate) fn package_lease_for_sheet(&self, sheet: &str) -> Option<StagedPackageLease> {
537 self.packages.get(sheet).map(|package| StagedPackageLease {
538 generation: package.generation,
539 family_count: package.family_count,
540 })
541 }
542
543 pub(crate) fn package_lease_matches(&self, sheet: &str, lease: StagedPackageLease) -> bool {
544 self.packages.get(sheet).is_some_and(|package| {
545 package.generation == lease.generation && package.family_count == lease.family_count
546 })
547 }
548
549 pub(crate) fn leases_in_region(
550 &self,
551 sheet: &str,
552 start_row: u32,
553 start_col: u32,
554 end_row: u32,
555 end_col: u32,
556 ) -> Vec<StagedFormulaLease> {
557 let mut leases = self
558 .sheets
559 .get(sheet)
560 .into_iter()
561 .flat_map(|entries| entries.range((start_row, 0)..=(end_row, u32::MAX)))
562 .filter_map(|(&(row, col), entry)| {
563 (col >= start_col && col <= end_col).then_some(StagedFormulaLease {
564 row,
565 col,
566 generation: entry.generation,
567 insertion_order: entry.insertion_order,
568 })
569 })
570 .collect::<Vec<_>>();
571 leases.sort_by_key(|lease| lease.insertion_order);
572 leases
573 }
574
575 pub(crate) fn leases_for_sheet(&self, sheet: &str) -> Vec<StagedFormulaLease> {
576 self.leases_in_region(sheet, 1, 1, u32::MAX, u32::MAX)
577 }
578
579 pub(crate) fn all_leases(&self) -> Vec<(String, StagedFormulaLease)> {
580 let mut leases = self
581 .sheets
582 .iter()
583 .flat_map(|(sheet, entries)| {
584 entries.iter().map(move |(&(row, col), entry)| {
585 (
586 sheet.clone(),
587 StagedFormulaLease {
588 row,
589 col,
590 generation: entry.generation,
591 insertion_order: entry.insertion_order,
592 },
593 )
594 })
595 })
596 .collect::<Vec<_>>();
597 leases.sort_by_key(|(_, lease)| lease.insertion_order);
598 leases
599 }
600
601 pub(crate) fn lease_matches(&self, sheet: &str, lease: StagedFormulaLease) -> bool {
602 self.sheets
603 .get(sheet)
604 .and_then(|entries| entries.get(&(lease.row, lease.col)))
605 .is_some_and(|entry| {
606 entry.generation == lease.generation
607 && entry.insertion_order == lease.insertion_order
608 })
609 }
610
611 pub(crate) fn ordinary_count(&self) -> usize {
612 self.sheets.values().map(BTreeMap::len).sum()
613 }
614
615 #[cfg(test)]
616 pub(crate) fn package_count(&self) -> usize {
617 self.packages.len()
618 }
619}
620
621#[deprecated(since = "0.8.0", note = "renamed to TargetEvalOptions")]
624pub type PrepareTargetsOptions<'a> = TargetEvalOptions<'a>;